Merge branch 'master' into b-5345-prometheus-metric-label-conflict
This commit is contained in:
commit
0436f70975
|
@ -7,6 +7,9 @@ IMPROVEMENTS:
|
|||
|
||||
BUG FIXES:
|
||||
|
||||
* drivers: Fixed an issue preventing external driver plugins from launching executor process [[GH-5726](https://github.com/hashicorp/nomad/issues/5726)]
|
||||
* drivers/docker: Fixed a bug mounting relative paths on Windows [[GH-5811](https://github.com/hashicorp/nomad/issues/5811)]
|
||||
* drivers/exec: Upgraded libcontainer dependency to avoid zombie `runc:[1:CHILD]]` processes [[GH-5851](https://github.com/hashicorp/nomad/issues/5851)]
|
||||
* metrics: upgrade prometheus client to avoid label conflicts [[GH-5850](https://github.com/hashicorp/nomad/issues/5850)]
|
||||
* ui: Fixed ability to click sort arrow to change sort direction [[GH-5833](https://github.com/hashicorp/nomad/pull/5833)]
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@ package logmon
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/client/logmon/proto"
|
||||
"github.com/hashicorp/nomad/helper/discover"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
@ -16,7 +16,7 @@ import (
|
|||
// TODO: Integrate with base plugin loader
|
||||
func LaunchLogMon(logger hclog.Logger, reattachConfig *plugin.ReattachConfig) (LogMon, *plugin.Client, error) {
|
||||
logger = logger.Named("logmon")
|
||||
bin, err := discover.NomadExecutable()
|
||||
bin, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package logmon
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
)
|
||||
|
||||
// Install a plugin cli handler to ease working with tests
|
||||
// and external plugins.
|
||||
// This init() must be initialized last in package required by the child plugin
|
||||
// process. It's recommended to avoid any other `init()` or inline any necessary calls
|
||||
// here. See eeaa95d commit message for more details.
|
||||
func init() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "logmon" {
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
JSONFormat: true,
|
||||
Name: "logmon",
|
||||
})
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: base.Handshake,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
"logmon": NewPlugin(NewLogMon(logger)),
|
||||
},
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
Logger: logger,
|
||||
})
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/hashicorp/nomad/command/agent"
|
||||
"github.com/hashicorp/nomad/drivers/docker/docklog"
|
||||
"github.com/hashicorp/nomad/version"
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
"github.com/mitchellh/cli"
|
||||
|
@ -237,11 +236,6 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
|
|||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
docklog.PluginName: func() (cli.Command, error) {
|
||||
return &DockerLoggerPluginCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"eval": func() (cli.Command, error) {
|
||||
return &EvalCommand{
|
||||
Meta: meta,
|
||||
|
@ -262,11 +256,6 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
|
|||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"executor": func() (cli.Command, error) {
|
||||
return &ExecutorPluginCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"fs": func() (cli.Command, error) {
|
||||
return &AllocFSCommand{
|
||||
Meta: meta,
|
||||
|
@ -372,11 +361,6 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
|
|||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"logmon": func() (cli.Command, error) {
|
||||
return &LogMonPluginCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"logs": func() (cli.Command, error) {
|
||||
return &AllocLogsCommand{
|
||||
Meta: meta,
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/drivers/docker/docklog"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
)
|
||||
|
||||
type DockerLoggerPluginCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (e *DockerLoggerPluginCommand) Help() string {
|
||||
helpText := `
|
||||
This is a command used by Nomad internally to launch the docker logger process"
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (e *DockerLoggerPluginCommand) Synopsis() string {
|
||||
return "internal - launch a docker logger plugin"
|
||||
}
|
||||
|
||||
func (e *DockerLoggerPluginCommand) Run(args []string) int {
|
||||
logger := log.New(&log.LoggerOptions{
|
||||
Level: log.Trace,
|
||||
JSONFormat: true,
|
||||
Name: docklog.PluginName,
|
||||
})
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: base.Handshake,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
docklog.PluginName: docklog.NewPlugin(docklog.NewDockerLogger(logger)),
|
||||
},
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
Logger: logger,
|
||||
})
|
||||
return 0
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
)
|
||||
|
||||
type ExecutorPluginCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (e *ExecutorPluginCommand) Help() string {
|
||||
helpText := `
|
||||
This is a command used by Nomad internally to launch an executor plugin"
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (e *ExecutorPluginCommand) Synopsis() string {
|
||||
return "internal - launch an executor plugin"
|
||||
}
|
||||
|
||||
func (e *ExecutorPluginCommand) Run(args []string) int {
|
||||
if len(args) != 1 {
|
||||
e.Ui.Error("json configuration not provided")
|
||||
return 1
|
||||
}
|
||||
|
||||
config := args[0]
|
||||
var executorConfig executor.ExecutorConfig
|
||||
if err := json.Unmarshal([]byte(config), &executorConfig); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(executorConfig.LogFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
e.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
// Create the logger
|
||||
logger := log.New(&log.LoggerOptions{
|
||||
Level: hclog.LevelFromString(executorConfig.LogLevel),
|
||||
JSONFormat: true,
|
||||
Output: f,
|
||||
})
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: base.Handshake,
|
||||
Plugins: executor.GetPluginMap(
|
||||
logger,
|
||||
executorConfig.FSIsolation,
|
||||
),
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
Logger: logger,
|
||||
})
|
||||
return 0
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/client/logmon"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
)
|
||||
|
||||
type LogMonPluginCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (e *LogMonPluginCommand) Help() string {
|
||||
helpText := `
|
||||
This is a command used by Nomad internally to launch the logmon process"
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (e *LogMonPluginCommand) Synopsis() string {
|
||||
return "internal - launch a logmon plugin"
|
||||
}
|
||||
|
||||
func (e *LogMonPluginCommand) Run(args []string) int {
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
JSONFormat: true,
|
||||
Name: "logmon",
|
||||
})
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: base.Handshake,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
"logmon": logmon.NewPlugin(logmon.NewLogMon(logger)),
|
||||
},
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
Logger: logger,
|
||||
})
|
||||
return 0
|
||||
}
|
|
@ -2,12 +2,12 @@ package docklog
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/drivers/docker/docklog/proto"
|
||||
"github.com/hashicorp/nomad/helper/discover"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
@ -17,7 +17,7 @@ const PluginName = "docker_logger"
|
|||
// LaunchDockerLogger launches an instance of DockerLogger
|
||||
func LaunchDockerLogger(logger hclog.Logger) (DockerLogger, *plugin.Client, error) {
|
||||
logger = logger.Named(PluginName)
|
||||
bin, err := discover.NomadExecutable()
|
||||
bin, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package docklog
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
)
|
||||
|
||||
// Install a plugin cli handler to ease working with tests
|
||||
// and external plugins.
|
||||
// This init() must be initialized last in package required by the child plugin
|
||||
// process. It's recommended to avoid any other `init()` or inline any necessary calls
|
||||
// here. See eeaa95d commit message for more details.
|
||||
func init() {
|
||||
if len(os.Args) > 1 && os.Args[1] == PluginName {
|
||||
logger := log.New(&log.LoggerOptions{
|
||||
Level: log.Trace,
|
||||
JSONFormat: true,
|
||||
Name: PluginName,
|
||||
})
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: base.Handshake,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
PluginName: NewPlugin(NewDockerLogger(logger)),
|
||||
},
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
Logger: logger,
|
||||
})
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package docker
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -12,7 +11,6 @@ import (
|
|||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/volume/mounts"
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
|
@ -230,26 +228,23 @@ func parseVolumeSpec(volBind, os string) (hostPath string, containerPath string,
|
|||
}
|
||||
|
||||
func parseVolumeSpecWindows(volBind string) (hostPath string, containerPath string, mode string, err error) {
|
||||
parser := mounts.NewParser("windows")
|
||||
m, err := parser.ParseMountRaw(volBind, "")
|
||||
parts, err := windowsSplitRawSpec(volBind, rxDestination)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
return "", "", "", fmt.Errorf("not <src>:<destination> format")
|
||||
}
|
||||
|
||||
src := m.Source
|
||||
if src == "" && strings.Contains(volBind, m.Name) {
|
||||
src = m.Name
|
||||
if len(parts) < 2 {
|
||||
return "", "", "", fmt.Errorf("not <src>:<destination> format")
|
||||
}
|
||||
|
||||
if src == "" {
|
||||
return "", "", "", errors.New("missing host path")
|
||||
hostPath = parts[0]
|
||||
containerPath = parts[1]
|
||||
|
||||
if len(parts) > 2 {
|
||||
mode = parts[2]
|
||||
}
|
||||
|
||||
if m.Destination == "" {
|
||||
return "", "", "", errors.New("container path is empty")
|
||||
}
|
||||
|
||||
return src, m.Destination, m.Mode, nil
|
||||
return
|
||||
}
|
||||
|
||||
func parseVolumeSpecLinux(volBind string) (hostPath string, containerPath string, mode string, err error) {
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// This code is taken from github.com/docker/volume/mounts/windows_parser.go
|
||||
// See https://github.com/moby/moby/blob/master/LICENSE for the license, Apache License 2.0 at this time.
|
||||
|
||||
const (
|
||||
// Spec should be in the format [source:]destination[:mode]
|
||||
//
|
||||
// Examples: c:\foo bar:d:rw
|
||||
// c:\foo:d:\bar
|
||||
// myname:d:
|
||||
// d:\
|
||||
//
|
||||
// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
|
||||
// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
|
||||
// test is https://regex-golang.appspot.com/assets/html/index.html
|
||||
//
|
||||
// Useful link for referencing named capturing groups:
|
||||
// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
|
||||
//
|
||||
// There are three match groups: source, destination and mode.
|
||||
//
|
||||
|
||||
// rxHostDir is the first option of a source
|
||||
rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*`
|
||||
// rxName is the second option of a source
|
||||
rxName = `[^\\/:*?"<>|\r\n]+`
|
||||
|
||||
// RXReservedNames are reserved names not possible on Windows
|
||||
rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
|
||||
|
||||
// rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
|
||||
rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
|
||||
// rxSource is the combined possibilities for a source
|
||||
rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?`
|
||||
|
||||
// Source. Can be either a host directory, a name, or omitted:
|
||||
// HostDir:
|
||||
// - Essentially using the folder solution from
|
||||
// https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
|
||||
// but adding case insensitivity.
|
||||
// - Must be an absolute path such as c:\path
|
||||
// - Can include spaces such as `c:\program files`
|
||||
// - And then followed by a colon which is not in the capture group
|
||||
// - And can be optional
|
||||
// Name:
|
||||
// - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
|
||||
// - And then followed by a colon which is not in the capture group
|
||||
// - And can be optional
|
||||
|
||||
// rxDestination is the regex expression for the mount destination
|
||||
rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))`
|
||||
|
||||
// Destination (aka container path):
|
||||
// - Variation on hostdir but can be a drive followed by colon as well
|
||||
// - If a path, must be absolute. Can include spaces
|
||||
// - Drive cannot be c: (explicitly checked in code, not RegEx)
|
||||
|
||||
// rxMode is the regex expression for the mode of the mount
|
||||
// Mode (optional):
|
||||
// - Hopefully self explanatory in comparison to above regex's.
|
||||
// - Colon is not in the capture group
|
||||
rxMode = `(:(?P<mode>(?i)ro|rw))?`
|
||||
)
|
||||
|
||||
func errInvalidSpec(spec string) error {
|
||||
return errors.Errorf("invalid volume specification: '%s'", spec)
|
||||
}
|
||||
|
||||
type fileInfoProvider interface {
|
||||
fileInfo(path string) (exist, isDir bool, err error)
|
||||
}
|
||||
|
||||
type defaultFileInfoProvider struct {
|
||||
}
|
||||
|
||||
func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return false, false, err
|
||||
}
|
||||
return false, false, nil
|
||||
}
|
||||
return true, fi.IsDir(), nil
|
||||
}
|
||||
|
||||
var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}
|
||||
|
||||
func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
|
||||
specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`)
|
||||
match := specExp.FindStringSubmatch(strings.ToLower(raw))
|
||||
|
||||
// Must have something back
|
||||
if len(match) == 0 {
|
||||
return nil, errInvalidSpec(raw)
|
||||
}
|
||||
|
||||
var split []string
|
||||
matchgroups := make(map[string]string)
|
||||
// Pull out the sub expressions from the named capture groups
|
||||
for i, name := range specExp.SubexpNames() {
|
||||
matchgroups[name] = strings.ToLower(match[i])
|
||||
}
|
||||
if source, exists := matchgroups["source"]; exists {
|
||||
if source != "" {
|
||||
split = append(split, source)
|
||||
}
|
||||
}
|
||||
if destination, exists := matchgroups["destination"]; exists {
|
||||
if destination != "" {
|
||||
split = append(split, destination)
|
||||
}
|
||||
}
|
||||
if mode, exists := matchgroups["mode"]; exists {
|
||||
if mode != "" {
|
||||
split = append(split, mode)
|
||||
}
|
||||
}
|
||||
// Fix #26329. If the destination appears to be a file, and the source is null,
|
||||
// it may be because we've fallen through the possible naming regex and hit a
|
||||
// situation where the user intention was to map a file into a container through
|
||||
// a local volume, but this is not supported by the platform.
|
||||
if matchgroups["source"] == "" && matchgroups["destination"] != "" {
|
||||
volExp := regexp.MustCompile(`^` + rxName + `$`)
|
||||
reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`)
|
||||
|
||||
if volExp.MatchString(matchgroups["destination"]) {
|
||||
if reservedNameExp.MatchString(matchgroups["destination"]) {
|
||||
return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
|
||||
}
|
||||
} else {
|
||||
|
||||
exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"])
|
||||
if exists && !isDir {
|
||||
return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return split, nil
|
||||
}
|
|
@ -3,13 +3,13 @@ package executor
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor/proto"
|
||||
"github.com/hashicorp/nomad/helper/discover"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
)
|
||||
|
||||
|
@ -32,7 +32,7 @@ func CreateExecutor(logger hclog.Logger, driverConfig *base.ClientDriverConfig,
|
|||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create executor config: %v", err)
|
||||
}
|
||||
bin, err := discover.NomadExecutable()
|
||||
bin, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to find the nomad binary: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package executor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
)
|
||||
|
||||
// Install a plugin cli handler to ease working with tests
|
||||
// and external plugins.
|
||||
// This init() must be initialized last in package required by the child plugin
|
||||
// process. It's recommended to avoid any other `init()` or inline any necessary calls
|
||||
// here. See eeaa95d commit message for more details.
|
||||
func init() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "executor" {
|
||||
if len(os.Args) != 3 {
|
||||
hclog.L().Error("json configuration not provided")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config := os.Args[2]
|
||||
var executorConfig ExecutorConfig
|
||||
if err := json.Unmarshal([]byte(config), &executorConfig); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(executorConfig.LogFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
hclog.L().Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create the logger
|
||||
logger := log.New(&log.LoggerOptions{
|
||||
Level: hclog.LevelFromString(executorConfig.LogLevel),
|
||||
JSONFormat: true,
|
||||
Output: f,
|
||||
})
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: base.Handshake,
|
||||
Plugins: GetPluginMap(
|
||||
logger,
|
||||
executorConfig.FSIsolation,
|
||||
),
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
Logger: logger,
|
||||
})
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
|
@ -458,3 +458,50 @@ func TestParseNullFields(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseUnknown(t *testing.T) {
|
||||
spec := hclspec.NewObject(map[string]*hclspec.Spec{
|
||||
"string_field": hclspec.NewAttr("string_field", "string", false),
|
||||
"map_field": hclspec.NewAttr("map_field", "map(string)", false),
|
||||
"list_field": hclspec.NewAttr("list_field", "map(string)", false),
|
||||
"map_list_field": hclspec.NewAttr("map_list_field", "list(map(string))", false),
|
||||
})
|
||||
cSpec, diags := hclspecutils.Convert(spec)
|
||||
require.False(t, diags.HasErrors())
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
hcl string
|
||||
}{
|
||||
{
|
||||
"string field",
|
||||
`config { string_field = "${MYENV}" }`,
|
||||
},
|
||||
{
|
||||
"map_field",
|
||||
`config { map_field { key = "${MYENV}" }}`,
|
||||
},
|
||||
{
|
||||
"list_field",
|
||||
`config { list_field = ["${MYENV}"]}`,
|
||||
},
|
||||
{
|
||||
"map_list_field",
|
||||
`config { map_list_field { key = "${MYENV}"}}`,
|
||||
},
|
||||
}
|
||||
|
||||
vars := map[string]cty.Value{}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
inter := hclutils.HclConfigToInterface(t, c.hcl)
|
||||
|
||||
ctyValue, diag := hclutils.ParseHclInterface(inter, cSpec, vars)
|
||||
t.Logf("parsed: %# v", pretty.Formatter(ctyValue))
|
||||
|
||||
require.True(t, diag.HasErrors())
|
||||
require.Contains(t, diag.Errs()[0].Error(), "no variable named")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -417,6 +417,17 @@ Token:
|
|||
p.recoverAfterBodyItem()
|
||||
}
|
||||
|
||||
// We must never produce a nil body, since the caller may attempt to
|
||||
// do analysis of a partial result when there's an error, so we'll
|
||||
// insert a placeholder if we otherwise failed to produce a valid
|
||||
// body due to one of the syntax error paths above.
|
||||
if body == nil && diags.HasErrors() {
|
||||
body = &Body{
|
||||
SrcRange: hcl.RangeBetween(oBrace.Range, cBraceRange),
|
||||
EndRange: cBraceRange,
|
||||
}
|
||||
}
|
||||
|
||||
return &Block{
|
||||
Type: blockType,
|
||||
Labels: labels,
|
||||
|
|
|
@ -2,10 +2,10 @@ package hclsyntax
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/apparentlymart/go-textseg/textseg"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/apparentlymart/go-textseg/textseg"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -63,8 +63,16 @@ func scanTokens(data []byte, filename string, start hcl.Pos, mode scanMode) []To
|
|||
BeginHeredocTmpl = '<<' ('-')? Ident Newline;
|
||||
|
||||
Comment = (
|
||||
("#" (any - EndOfLine)* EndOfLine) |
|
||||
("//" (any - EndOfLine)* EndOfLine) |
|
||||
# The :>> operator in these is a "finish-guarded concatenation",
|
||||
# which terminates the sequence on its left when it completes
|
||||
# the sequence on its right.
|
||||
# In the single-line comment cases this is allowing us to make
|
||||
# the trailing EndOfLine optional while still having the overall
|
||||
# pattern terminate. In the multi-line case it ensures that
|
||||
# the first comment in the file ends at the first */, rather than
|
||||
# gobbling up all of the "any*" until the _final_ */ in the file.
|
||||
("#" (any - EndOfLine)* :>> EndOfLine?) |
|
||||
("//" (any - EndOfLine)* :>> EndOfLine?) |
|
||||
("/*" any* :>> "*/")
|
||||
);
|
||||
|
||||
|
@ -218,29 +226,35 @@ func scanTokens(data []byte, filename string, start hcl.Pos, mode scanMode) []To
|
|||
TemplateInterp = "${" ("~")?;
|
||||
TemplateControl = "%{" ("~")?;
|
||||
EndStringTmpl = '"';
|
||||
StringLiteralChars = (AnyUTF8 - ("\r"|"\n"));
|
||||
NewlineChars = ("\r"|"\n");
|
||||
NewlineCharsSeq = NewlineChars+;
|
||||
StringLiteralChars = (AnyUTF8 - NewlineChars);
|
||||
TemplateIgnoredNonBrace = (^'{' %{ fhold; });
|
||||
TemplateNotInterp = '$' (TemplateIgnoredNonBrace | TemplateInterp);
|
||||
TemplateNotControl = '%' (TemplateIgnoredNonBrace | TemplateControl);
|
||||
QuotedStringLiteralWithEsc = ('\\' StringLiteralChars) | (StringLiteralChars - ("$" | '%' | '"' | "\\"));
|
||||
TemplateStringLiteral = (
|
||||
('$' ^'{' %{ fhold; }) |
|
||||
('%' ^'{' %{ fhold; }) |
|
||||
('\\' StringLiteralChars) |
|
||||
(StringLiteralChars - ("$" | '%' | '"'))
|
||||
)+;
|
||||
(TemplateNotInterp) |
|
||||
(TemplateNotControl) |
|
||||
(QuotedStringLiteralWithEsc)+
|
||||
);
|
||||
HeredocStringLiteral = (
|
||||
('$' ^'{' %{ fhold; }) |
|
||||
('%' ^'{' %{ fhold; }) |
|
||||
(StringLiteralChars - ("$" | '%'))
|
||||
)*;
|
||||
(TemplateNotInterp) |
|
||||
(TemplateNotControl) |
|
||||
(StringLiteralChars - ("$" | '%'))*
|
||||
);
|
||||
BareStringLiteral = (
|
||||
('$' ^'{') |
|
||||
('%' ^'{') |
|
||||
(StringLiteralChars - ("$" | '%'))
|
||||
)* Newline?;
|
||||
(TemplateNotInterp) |
|
||||
(TemplateNotControl) |
|
||||
(StringLiteralChars - ("$" | '%'))*
|
||||
) Newline?;
|
||||
|
||||
stringTemplate := |*
|
||||
TemplateInterp => beginTemplateInterp;
|
||||
TemplateControl => beginTemplateControl;
|
||||
EndStringTmpl => endStringTemplate;
|
||||
TemplateStringLiteral => { token(TokenQuotedLit); };
|
||||
NewlineCharsSeq => { token(TokenQuotedNewline); };
|
||||
AnyUTF8 => { token(TokenInvalid); };
|
||||
BrokenUTF8 => { token(TokenBadUTF8); };
|
||||
*|;
|
||||
|
|
|
@ -279,7 +279,11 @@ func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
|||
}
|
||||
|
||||
func (b *Body) MissingItemRange() hcl.Range {
|
||||
return b.EndRange
|
||||
return hcl.Range{
|
||||
Filename: b.SrcRange.Filename,
|
||||
Start: b.SrcRange.Start,
|
||||
End: b.SrcRange.Start,
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes is the collection of attribute definitions within a body.
|
||||
|
|
|
@ -85,17 +85,18 @@ const (
|
|||
// things that might work in other languages they are familiar with, or
|
||||
// simply make incorrect assumptions about the HCL language.
|
||||
|
||||
TokenBitwiseAnd TokenType = '&'
|
||||
TokenBitwiseOr TokenType = '|'
|
||||
TokenBitwiseNot TokenType = '~'
|
||||
TokenBitwiseXor TokenType = '^'
|
||||
TokenStarStar TokenType = '➚'
|
||||
TokenApostrophe TokenType = '\''
|
||||
TokenBacktick TokenType = '`'
|
||||
TokenSemicolon TokenType = ';'
|
||||
TokenTabs TokenType = '␉'
|
||||
TokenInvalid TokenType = '<27>'
|
||||
TokenBadUTF8 TokenType = '💩'
|
||||
TokenBitwiseAnd TokenType = '&'
|
||||
TokenBitwiseOr TokenType = '|'
|
||||
TokenBitwiseNot TokenType = '~'
|
||||
TokenBitwiseXor TokenType = '^'
|
||||
TokenStarStar TokenType = '➚'
|
||||
TokenApostrophe TokenType = '\''
|
||||
TokenBacktick TokenType = '`'
|
||||
TokenSemicolon TokenType = ';'
|
||||
TokenTabs TokenType = '␉'
|
||||
TokenInvalid TokenType = '<27>'
|
||||
TokenBadUTF8 TokenType = '💩'
|
||||
TokenQuotedNewline TokenType = ''
|
||||
|
||||
// TokenNil is a placeholder for when a token is required but none is
|
||||
// available, e.g. when reporting errors. The scanner will never produce
|
||||
|
@ -285,6 +286,13 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
|
|||
|
||||
toldBadUTF8++
|
||||
}
|
||||
case TokenQuotedNewline:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid multi-line string",
|
||||
Detail: "Quoted strings may not be split over multiple lines. To produce a multi-line string, either use the \\n escape to represent a newline character or use the \"heredoc\" multi-line template syntax.",
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
case TokenInvalid:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
|
|
|
@ -4,7 +4,67 @@ package hclsyntax
|
|||
|
||||
import "strconv"
|
||||
|
||||
const _TokenType_name = "TokenNilTokenNewlineTokenBangTokenPercentTokenBitwiseAndTokenApostropheTokenOParenTokenCParenTokenStarTokenPlusTokenCommaTokenMinusTokenDotTokenSlashTokenColonTokenSemicolonTokenLessThanTokenEqualTokenGreaterThanTokenQuestionTokenCommentTokenOHeredocTokenIdentTokenNumberLitTokenQuotedLitTokenStringLitTokenOBrackTokenCBrackTokenBitwiseXorTokenBacktickTokenCHeredocTokenOBraceTokenBitwiseOrTokenCBraceTokenBitwiseNotTokenOQuoteTokenCQuoteTokenTemplateControlTokenEllipsisTokenFatArrowTokenTemplateSeqEndTokenAndTokenOrTokenTemplateInterpTokenEqualOpTokenNotEqualTokenLessThanEqTokenGreaterThanEqTokenEOFTokenTabsTokenStarStarTokenInvalidTokenBadUTF8"
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TokenOBrace-123]
|
||||
_ = x[TokenCBrace-125]
|
||||
_ = x[TokenOBrack-91]
|
||||
_ = x[TokenCBrack-93]
|
||||
_ = x[TokenOParen-40]
|
||||
_ = x[TokenCParen-41]
|
||||
_ = x[TokenOQuote-171]
|
||||
_ = x[TokenCQuote-187]
|
||||
_ = x[TokenOHeredoc-72]
|
||||
_ = x[TokenCHeredoc-104]
|
||||
_ = x[TokenStar-42]
|
||||
_ = x[TokenSlash-47]
|
||||
_ = x[TokenPlus-43]
|
||||
_ = x[TokenMinus-45]
|
||||
_ = x[TokenPercent-37]
|
||||
_ = x[TokenEqual-61]
|
||||
_ = x[TokenEqualOp-8788]
|
||||
_ = x[TokenNotEqual-8800]
|
||||
_ = x[TokenLessThan-60]
|
||||
_ = x[TokenLessThanEq-8804]
|
||||
_ = x[TokenGreaterThan-62]
|
||||
_ = x[TokenGreaterThanEq-8805]
|
||||
_ = x[TokenAnd-8743]
|
||||
_ = x[TokenOr-8744]
|
||||
_ = x[TokenBang-33]
|
||||
_ = x[TokenDot-46]
|
||||
_ = x[TokenComma-44]
|
||||
_ = x[TokenEllipsis-8230]
|
||||
_ = x[TokenFatArrow-8658]
|
||||
_ = x[TokenQuestion-63]
|
||||
_ = x[TokenColon-58]
|
||||
_ = x[TokenTemplateInterp-8747]
|
||||
_ = x[TokenTemplateControl-955]
|
||||
_ = x[TokenTemplateSeqEnd-8718]
|
||||
_ = x[TokenQuotedLit-81]
|
||||
_ = x[TokenStringLit-83]
|
||||
_ = x[TokenNumberLit-78]
|
||||
_ = x[TokenIdent-73]
|
||||
_ = x[TokenComment-67]
|
||||
_ = x[TokenNewline-10]
|
||||
_ = x[TokenEOF-9220]
|
||||
_ = x[TokenBitwiseAnd-38]
|
||||
_ = x[TokenBitwiseOr-124]
|
||||
_ = x[TokenBitwiseNot-126]
|
||||
_ = x[TokenBitwiseXor-94]
|
||||
_ = x[TokenStarStar-10138]
|
||||
_ = x[TokenApostrophe-39]
|
||||
_ = x[TokenBacktick-96]
|
||||
_ = x[TokenSemicolon-59]
|
||||
_ = x[TokenTabs-9225]
|
||||
_ = x[TokenInvalid-65533]
|
||||
_ = x[TokenBadUTF8-128169]
|
||||
_ = x[TokenQuotedNewline-9252]
|
||||
_ = x[TokenNil-0]
|
||||
}
|
||||
|
||||
const _TokenType_name = "TokenNilTokenNewlineTokenBangTokenPercentTokenBitwiseAndTokenApostropheTokenOParenTokenCParenTokenStarTokenPlusTokenCommaTokenMinusTokenDotTokenSlashTokenColonTokenSemicolonTokenLessThanTokenEqualTokenGreaterThanTokenQuestionTokenCommentTokenOHeredocTokenIdentTokenNumberLitTokenQuotedLitTokenStringLitTokenOBrackTokenCBrackTokenBitwiseXorTokenBacktickTokenCHeredocTokenOBraceTokenBitwiseOrTokenCBraceTokenBitwiseNotTokenOQuoteTokenCQuoteTokenTemplateControlTokenEllipsisTokenFatArrowTokenTemplateSeqEndTokenAndTokenOrTokenTemplateInterpTokenEqualOpTokenNotEqualTokenLessThanEqTokenGreaterThanEqTokenEOFTokenTabsTokenQuotedNewlineTokenStarStarTokenInvalidTokenBadUTF8"
|
||||
|
||||
var _TokenType_map = map[TokenType]string{
|
||||
0: _TokenType_name[0:8],
|
||||
|
@ -57,9 +117,10 @@ var _TokenType_map = map[TokenType]string{
|
|||
8805: _TokenType_name[577:595],
|
||||
9220: _TokenType_name[595:603],
|
||||
9225: _TokenType_name[603:612],
|
||||
10138: _TokenType_name[612:625],
|
||||
65533: _TokenType_name[625:637],
|
||||
128169: _TokenType_name[637:649],
|
||||
9252: _TokenType_name[612:630],
|
||||
10138: _TokenType_name[630:643],
|
||||
65533: _TokenType_name[643:655],
|
||||
128169: _TokenType_name[655:667],
|
||||
}
|
||||
|
||||
func (i TokenType) String() string {
|
||||
|
|
|
@ -153,7 +153,7 @@ func byteCanStartKeyword(b byte) bool {
|
|||
// in the parser, where we can generate better diagnostics.
|
||||
// So e.g. we want to be able to say:
|
||||
// unrecognized keyword "True". Did you mean "true"?
|
||||
case b >= 'a' || b <= 'z' || b >= 'A' || b <= 'Z':
|
||||
case isAlphabetical(b):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -167,7 +167,7 @@ Byte:
|
|||
for i = 0; i < len(buf); i++ {
|
||||
b := buf[i]
|
||||
switch {
|
||||
case (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_':
|
||||
case isAlphabetical(b) || b == '_':
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column++
|
||||
default:
|
||||
|
@ -291,3 +291,7 @@ func posRange(start, end pos) hcl.Range {
|
|||
func (t token) GoString() string {
|
||||
return fmt.Sprintf("json.token{json.%s, []byte(%q), %#v}", t.Type, t.Bytes, t.Range)
|
||||
}
|
||||
|
||||
func isAlphabetical(b byte) bool {
|
||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
|
||||
}
|
||||
|
|
|
@ -416,12 +416,14 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|||
case *booleanVal:
|
||||
return cty.BoolVal(v.Value), nil
|
||||
case *arrayVal:
|
||||
var diags hcl.Diagnostics
|
||||
vals := []cty.Value{}
|
||||
for _, jsonVal := range v.Values {
|
||||
val, _ := (&expression{src: jsonVal}).Value(ctx)
|
||||
val, valDiags := (&expression{src: jsonVal}).Value(ctx)
|
||||
vals = append(vals, val)
|
||||
diags = append(diags, valDiags...)
|
||||
}
|
||||
return cty.TupleVal(vals), nil
|
||||
return cty.TupleVal(vals), diags
|
||||
case *objectVal:
|
||||
var diags hcl.Diagnostics
|
||||
attrs := map[string]cty.Value{}
|
||||
|
|
|
@ -2,6 +2,7 @@ package hcl
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
|
@ -84,6 +85,27 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
|
|||
}
|
||||
}
|
||||
if has.False() {
|
||||
// We have a more specialized error message for the situation of
|
||||
// using a fractional number to index into a sequence, because
|
||||
// that will tend to happen if the user is trying to use division
|
||||
// to calculate an index and not realizing that HCL does float
|
||||
// division rather than integer division.
|
||||
if (ty.IsListType() || ty.IsTupleType()) && key.Type().Equals(cty.Number) {
|
||||
if key.IsKnown() && !key.IsNull() {
|
||||
bf := key.AsBigFloat()
|
||||
if _, acc := bf.Int(nil); acc != big.Exact {
|
||||
return cty.DynamicVal, Diagnostics{
|
||||
{
|
||||
Severity: DiagError,
|
||||
Summary: "Invalid index",
|
||||
Detail: fmt.Sprintf("The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index (%g) has a fractional part.", bf),
|
||||
Subject: srcRange,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cty.DynamicVal, Diagnostics{
|
||||
{
|
||||
Severity: DiagError,
|
||||
|
|
|
@ -31,6 +31,9 @@ type Pos struct {
|
|||
Byte int
|
||||
}
|
||||
|
||||
// InitialPos is a suitable position to use to mark the start of a file.
|
||||
var InitialPos = Pos{Byte: 0, Line: 1, Column: 1}
|
||||
|
||||
// Range represents a span of characters between two positions in a source
|
||||
// file.
|
||||
//
|
||||
|
|
|
@ -29,8 +29,8 @@ type RangeScanner struct {
|
|||
err error // error from last scan, if any
|
||||
}
|
||||
|
||||
// Create a new RangeScanner for the given buffer, producing ranges for the
|
||||
// given filename.
|
||||
// NewRangeScanner creates a new RangeScanner for the given buffer, producing
|
||||
// ranges for the given filename.
|
||||
//
|
||||
// Since ranges have grapheme-cluster granularity rather than byte granularity,
|
||||
// the scanner will produce incorrect results if the given SplitFunc creates
|
||||
|
@ -39,15 +39,19 @@ type RangeScanner struct {
|
|||
// around individual UTF-8 sequences, which will split any multi-sequence
|
||||
// grapheme clusters.
|
||||
func NewRangeScanner(b []byte, filename string, cb bufio.SplitFunc) *RangeScanner {
|
||||
return NewRangeScannerFragment(b, filename, InitialPos, cb)
|
||||
}
|
||||
|
||||
// NewRangeScannerFragment is like NewRangeScanner but the ranges it produces
|
||||
// will be offset by the given starting position, which is appropriate for
|
||||
// sub-slices of a file, whereas NewRangeScanner assumes it is scanning an
|
||||
// entire file.
|
||||
func NewRangeScannerFragment(b []byte, filename string, start Pos, cb bufio.SplitFunc) *RangeScanner {
|
||||
return &RangeScanner{
|
||||
filename: filename,
|
||||
b: b,
|
||||
cb: cb,
|
||||
pos: Pos{
|
||||
Byte: 0,
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
},
|
||||
pos: start,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -79,13 +79,13 @@ func formatIndent(lines []formatLine) {
|
|||
netBrackets := 0
|
||||
for _, token := range line.lead {
|
||||
netBrackets += tokenBracketChange(token)
|
||||
}
|
||||
for _, token := range line.assign {
|
||||
netBrackets += tokenBracketChange(token)
|
||||
if token.Type == hclsyntax.TokenOHeredoc {
|
||||
inHeredoc = true
|
||||
}
|
||||
}
|
||||
for _, token := range line.assign {
|
||||
netBrackets += tokenBracketChange(token)
|
||||
}
|
||||
|
||||
switch {
|
||||
case netBrackets > 0:
|
||||
|
@ -248,8 +248,8 @@ func spaceAfterToken(subject, before, after *Token) bool {
|
|||
// Don't use spaces around attribute access dots
|
||||
return false
|
||||
|
||||
case after.Type == hclsyntax.TokenComma:
|
||||
// No space right before a comma in an argument list
|
||||
case after.Type == hclsyntax.TokenComma || after.Type == hclsyntax.TokenEllipsis:
|
||||
// No space right before a comma or ... in an argument list
|
||||
return false
|
||||
|
||||
case subject.Type == hclsyntax.TokenComma:
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
|
||||
|
||||
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.
|
41
vendor/github.com/konsorten/go-windows-terminal-sequences/README.md
generated
vendored
Normal file
41
vendor/github.com/konsorten/go-windows-terminal-sequences/README.md
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Windows Terminal Sequences
|
||||
|
||||
This library allow for enabling Windows terminal color support for Go.
|
||||
|
||||
See [Console Virtual Terminal Sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences) for details.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
sequences "github.com/konsorten/go-windows-terminal-sequences"
|
||||
)
|
||||
|
||||
func main() {
|
||||
sequences.EnableVirtualTerminalProcessing(syscall.Stdout, true)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Authors
|
||||
|
||||
The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de).
|
||||
|
||||
We thank all the authors who provided code to this library:
|
||||
|
||||
* Felix Kollmann
|
||||
* Nicolas Perraut
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2018 marvin + konsorten GmbH (open-source@konsorten.de)
|
||||
|
||||
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.
|
|
@ -0,0 +1 @@
|
|||
module github.com/konsorten/go-windows-terminal-sequences
|
36
vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go
generated
vendored
Normal file
36
vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
// +build windows
|
||||
|
||||
package sequences
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32Dll *syscall.LazyDLL = syscall.NewLazyDLL("Kernel32.dll")
|
||||
setConsoleMode *syscall.LazyProc = kernel32Dll.NewProc("SetConsoleMode")
|
||||
)
|
||||
|
||||
func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING uint32 = 0x4
|
||||
|
||||
var mode uint32
|
||||
err := syscall.GetConsoleMode(syscall.Stdout, &mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enable {
|
||||
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
} else {
|
||||
mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
}
|
||||
|
||||
ret, _, err := setConsoleMode.Call(uintptr(unsafe.Pointer(stream)), uintptr(mode))
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
11
vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go
generated
vendored
Normal file
11
vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// +build linux darwin
|
||||
|
||||
package sequences
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func EnableVirtualTerminalProcessing(stream uintptr, enable bool) error {
|
||||
return fmt.Errorf("windows only package")
|
||||
}
|
|
@ -18,6 +18,10 @@ func UseSystemd() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func NewSystemdCgroupsManager() (func(config *configs.Cgroup, paths map[string]string) cgroups.Manager, error) {
|
||||
return nil, fmt.Errorf("Systemd not supported")
|
||||
}
|
||||
|
||||
func (m *Manager) Apply(pid int) error {
|
||||
return fmt.Errorf("Systemd not supported")
|
||||
}
|
||||
|
|
12
vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go
generated
vendored
12
vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go
generated
vendored
|
@ -163,6 +163,18 @@ func UseSystemd() bool {
|
|||
return hasStartTransientUnit
|
||||
}
|
||||
|
||||
func NewSystemdCgroupsManager() (func(config *configs.Cgroup, paths map[string]string) cgroups.Manager, error) {
|
||||
if !systemdUtil.IsRunningSystemd() {
|
||||
return nil, fmt.Errorf("systemd not running on this host, can't use systemd as a cgroups.Manager")
|
||||
}
|
||||
return func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
|
||||
return &Manager{
|
||||
Cgroups: config,
|
||||
Paths: paths,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Apply(pid int) error {
|
||||
var (
|
||||
c = m.Cgroups
|
||||
|
|
|
@ -22,6 +22,13 @@ const (
|
|||
CgroupProcesses = "cgroup.procs"
|
||||
)
|
||||
|
||||
// HugePageSizeUnitList is a list of the units used by the linux kernel when
|
||||
// naming the HugePage control files.
|
||||
// https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt
|
||||
// TODO Since the kernel only use KB, MB and GB; TB and PB should be removed,
|
||||
// depends on https://github.com/docker/go-units/commit/a09cd47f892041a4fac473133d181f5aea6fa393
|
||||
var HugePageSizeUnitList = []string{"B", "KB", "MB", "GB", "TB", "PB"}
|
||||
|
||||
// https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
|
||||
func FindCgroupMountpoint(cgroupPath, subsystem string) (string, error) {
|
||||
mnt, _, err := FindCgroupMountpointAndRoot(cgroupPath, subsystem)
|
||||
|
@ -409,19 +416,26 @@ func RemovePaths(paths map[string]string) (err error) {
|
|||
}
|
||||
|
||||
func GetHugePageSize() ([]string, error) {
|
||||
var pageSizes []string
|
||||
sizeList := []string{"B", "kB", "MB", "GB", "TB", "PB"}
|
||||
files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages")
|
||||
if err != nil {
|
||||
return pageSizes, err
|
||||
return []string{}, err
|
||||
}
|
||||
var fileNames []string
|
||||
for _, st := range files {
|
||||
nameArray := strings.Split(st.Name(), "-")
|
||||
fileNames = append(fileNames, st.Name())
|
||||
}
|
||||
return getHugePageSizeFromFilenames(fileNames)
|
||||
}
|
||||
|
||||
func getHugePageSizeFromFilenames(fileNames []string) ([]string, error) {
|
||||
var pageSizes []string
|
||||
for _, fileName := range fileNames {
|
||||
nameArray := strings.Split(fileName, "-")
|
||||
pageSize, err := units.RAMInBytes(nameArray[1])
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList)
|
||||
sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, HugePageSizeUnitList)
|
||||
pageSizes = append(pageSizes, sizeString)
|
||||
}
|
||||
|
||||
|
|
|
@ -337,6 +337,7 @@ func (c *linuxContainer) start(process *Process) error {
|
|||
if err != nil {
|
||||
return newSystemErrorWithCause(err, "creating new parent process")
|
||||
}
|
||||
parent.forwardChildLogs()
|
||||
if err := parent.start(); err != nil {
|
||||
// terminate the process to ensure that it properly is reaped.
|
||||
if err := ignoreTerminateErrors(parent.terminate()); err != nil {
|
||||
|
@ -438,16 +439,24 @@ func (c *linuxContainer) includeExecFifo(cmd *exec.Cmd) error {
|
|||
}
|
||||
|
||||
func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
|
||||
parentPipe, childPipe, err := utils.NewSockPair("init")
|
||||
parentInitPipe, childInitPipe, err := utils.NewSockPair("init")
|
||||
if err != nil {
|
||||
return nil, newSystemErrorWithCause(err, "creating new init pipe")
|
||||
}
|
||||
cmd, err := c.commandTemplate(p, childPipe)
|
||||
messageSockPair := filePair{parentInitPipe, childInitPipe}
|
||||
|
||||
parentLogPipe, childLogPipe, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to create the log pipe: %s", err)
|
||||
}
|
||||
logFilePair := filePair{parentLogPipe, childLogPipe}
|
||||
|
||||
cmd, err := c.commandTemplate(p, childInitPipe, childLogPipe)
|
||||
if err != nil {
|
||||
return nil, newSystemErrorWithCause(err, "creating new command template")
|
||||
}
|
||||
if !p.Init {
|
||||
return c.newSetnsProcess(p, cmd, parentPipe, childPipe)
|
||||
return c.newSetnsProcess(p, cmd, messageSockPair, logFilePair)
|
||||
}
|
||||
|
||||
// We only set up fifoFd if we're not doing a `runc exec`. The historic
|
||||
|
@ -458,10 +467,10 @@ func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
|
|||
if err := c.includeExecFifo(cmd); err != nil {
|
||||
return nil, newSystemErrorWithCause(err, "including execfifo in cmd.Exec setup")
|
||||
}
|
||||
return c.newInitProcess(p, cmd, parentPipe, childPipe)
|
||||
return c.newInitProcess(p, cmd, messageSockPair, logFilePair)
|
||||
}
|
||||
|
||||
func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) {
|
||||
func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, childLogPipe *os.File) (*exec.Cmd, error) {
|
||||
cmd := exec.Command(c.initPath, c.initArgs[1:]...)
|
||||
cmd.Args[0] = c.initArgs[0]
|
||||
cmd.Stdin = p.Stdin
|
||||
|
@ -479,11 +488,18 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
|
|||
fmt.Sprintf("_LIBCONTAINER_CONSOLE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
|
||||
)
|
||||
}
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe)
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, childInitPipe)
|
||||
cmd.Env = append(cmd.Env,
|
||||
fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
|
||||
fmt.Sprintf("_LIBCONTAINER_STATEDIR=%s", c.root),
|
||||
)
|
||||
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, childLogPipe)
|
||||
cmd.Env = append(cmd.Env,
|
||||
fmt.Sprintf("_LIBCONTAINER_LOGPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
|
||||
fmt.Sprintf("_LIBCONTAINER_LOGLEVEL=%s", p.LogLevel),
|
||||
)
|
||||
|
||||
// NOTE: when running a container with no PID namespace and the parent process spawning the container is
|
||||
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
|
||||
// even with the parent still running.
|
||||
|
@ -493,7 +509,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
|
|||
return cmd, nil
|
||||
}
|
||||
|
||||
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) {
|
||||
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*initProcess, error) {
|
||||
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard))
|
||||
nsMaps := make(map[configs.NamespaceType]string)
|
||||
for _, ns := range c.config.Namespaces {
|
||||
|
@ -508,8 +524,8 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
|
|||
}
|
||||
init := &initProcess{
|
||||
cmd: cmd,
|
||||
childPipe: childPipe,
|
||||
parentPipe: parentPipe,
|
||||
messageSockPair: messageSockPair,
|
||||
logFilePair: logFilePair,
|
||||
manager: c.cgroupManager,
|
||||
intelRdtManager: c.intelRdtManager,
|
||||
config: c.newInitConfig(p),
|
||||
|
@ -522,7 +538,7 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
|
|||
return init, nil
|
||||
}
|
||||
|
||||
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*setnsProcess, error) {
|
||||
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*setnsProcess, error) {
|
||||
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns))
|
||||
state, err := c.currentState()
|
||||
if err != nil {
|
||||
|
@ -539,8 +555,8 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
|
|||
cgroupPaths: c.cgroupManager.GetPaths(),
|
||||
rootlessCgroups: c.config.RootlessCgroups,
|
||||
intelRdtPath: state.IntelRdtPath,
|
||||
childPipe: childPipe,
|
||||
parentPipe: parentPipe,
|
||||
messageSockPair: messageSockPair,
|
||||
logFilePair: logFilePair,
|
||||
config: c.newInitConfig(p),
|
||||
process: p,
|
||||
bootstrapData: data,
|
||||
|
|
|
@ -51,12 +51,11 @@ func InitArgs(args ...string) func(*LinuxFactory) error {
|
|||
// SystemdCgroups is an options func to configure a LinuxFactory to return
|
||||
// containers that use systemd to create and manage cgroups.
|
||||
func SystemdCgroups(l *LinuxFactory) error {
|
||||
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
|
||||
return &systemd.Manager{
|
||||
Cgroups: config,
|
||||
Paths: paths,
|
||||
}
|
||||
systemdCgroupsManager, err := systemd.NewSystemdCgroupsManager()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.NewCgroupsManager = systemdCgroupsManager
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package logs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
configureMutex = sync.Mutex{}
|
||||
// loggingConfigured will be set once logging has been configured via invoking `ConfigureLogging`.
|
||||
// Subsequent invocations of `ConfigureLogging` would be no-op
|
||||
loggingConfigured = false
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
LogLevel logrus.Level
|
||||
LogFormat string
|
||||
LogFilePath string
|
||||
LogPipeFd string
|
||||
}
|
||||
|
||||
func ForwardLogs(logPipe io.Reader) {
|
||||
lineReader := bufio.NewReader(logPipe)
|
||||
for {
|
||||
line, err := lineReader.ReadBytes('\n')
|
||||
if len(line) > 0 {
|
||||
processEntry(line)
|
||||
}
|
||||
if err == io.EOF {
|
||||
logrus.Debugf("log pipe has been closed: %+v", err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf("log pipe read error: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processEntry(text []byte) {
|
||||
type jsonLog struct {
|
||||
Level string `json:"level"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
var jl jsonLog
|
||||
if err := json.Unmarshal(text, &jl); err != nil {
|
||||
logrus.Errorf("failed to decode %q to json: %+v", text, err)
|
||||
return
|
||||
}
|
||||
|
||||
lvl, err := logrus.ParseLevel(jl.Level)
|
||||
if err != nil {
|
||||
logrus.Errorf("failed to parse log level %q: %v\n", jl.Level, err)
|
||||
return
|
||||
}
|
||||
logrus.StandardLogger().Logf(lvl, jl.Msg)
|
||||
}
|
||||
|
||||
func ConfigureLogging(config Config) error {
|
||||
configureMutex.Lock()
|
||||
defer configureMutex.Unlock()
|
||||
|
||||
if loggingConfigured {
|
||||
logrus.Debug("logging has already been configured")
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.SetLevel(config.LogLevel)
|
||||
|
||||
if config.LogPipeFd != "" {
|
||||
logPipeFdInt, err := strconv.Atoi(config.LogPipeFd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert _LIBCONTAINER_LOGPIPE environment variable value %q to int: %v", config.LogPipeFd, err)
|
||||
}
|
||||
logrus.SetOutput(os.NewFile(uintptr(logPipeFdInt), "logpipe"))
|
||||
} else if config.LogFilePath != "" {
|
||||
f, err := os.OpenFile(config.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.SetOutput(f)
|
||||
}
|
||||
|
||||
switch config.LogFormat {
|
||||
case "text":
|
||||
// retain logrus's default.
|
||||
case "json":
|
||||
logrus.SetFormatter(new(logrus.JSONFormatter))
|
||||
default:
|
||||
return fmt.Errorf("unknown log-format %q", config.LogFormat)
|
||||
}
|
||||
|
||||
loggingConfigured = true
|
||||
return nil
|
||||
}
|
|
@ -37,9 +37,6 @@ enum sync_t {
|
|||
SYNC_RECVPID_ACK = 0x43, /* PID was correctly received by parent. */
|
||||
SYNC_GRANDCHILD = 0x44, /* The grandchild is ready to run. */
|
||||
SYNC_CHILD_READY = 0x45, /* The child or grandchild is ready to return. */
|
||||
|
||||
/* XXX: This doesn't help with segfaults and other such issues. */
|
||||
SYNC_ERR = 0xFF, /* Fatal error, no turning back. The error code follows. */
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -95,6 +92,15 @@ struct nlconfig_t {
|
|||
size_t gidmappath_len;
|
||||
};
|
||||
|
||||
#define PANIC "panic"
|
||||
#define FATAL "fatal"
|
||||
#define ERROR "error"
|
||||
#define WARNING "warning"
|
||||
#define INFO "info"
|
||||
#define DEBUG "debug"
|
||||
|
||||
static int logfd = -1;
|
||||
|
||||
/*
|
||||
* List of netlink message types sent to us as part of bootstrapping the init.
|
||||
* These constants are defined in libcontainer/message_linux.go.
|
||||
|
@ -131,22 +137,34 @@ int setns(int fd, int nstype)
|
|||
}
|
||||
#endif
|
||||
|
||||
static void write_log_with_info(const char *level, const char *function, int line, const char *format, ...)
|
||||
{
|
||||
char message[1024] = {};
|
||||
|
||||
va_list args;
|
||||
|
||||
if (logfd < 0 || level == NULL)
|
||||
return;
|
||||
|
||||
va_start(args, format);
|
||||
if (vsnprintf(message, sizeof(message), format, args) < 0)
|
||||
return;
|
||||
va_end(args);
|
||||
|
||||
if (dprintf(logfd, "{\"level\":\"%s\", \"msg\": \"%s:%d %s\"}\n", level, function, line, message) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
#define write_log(level, fmt, ...) \
|
||||
write_log_with_info((level), __FUNCTION__, __LINE__, (fmt), ##__VA_ARGS__)
|
||||
|
||||
/* XXX: This is ugly. */
|
||||
static int syncfd = -1;
|
||||
|
||||
/* TODO(cyphar): Fix this so it correctly deals with syncT. */
|
||||
#define bail(fmt, ...) \
|
||||
do { \
|
||||
int ret = __COUNTER__ + 1; \
|
||||
fprintf(stderr, "nsenter: " fmt ": %m\n", ##__VA_ARGS__); \
|
||||
if (syncfd >= 0) { \
|
||||
enum sync_t s = SYNC_ERR; \
|
||||
if (write(syncfd, &s, sizeof(s)) != sizeof(s)) \
|
||||
fprintf(stderr, "nsenter: failed: write(s)"); \
|
||||
if (write(syncfd, &ret, sizeof(ret)) != sizeof(ret)) \
|
||||
fprintf(stderr, "nsenter: failed: write(ret)"); \
|
||||
} \
|
||||
exit(ret); \
|
||||
#define bail(fmt, ...) \
|
||||
do { \
|
||||
write_log(FATAL, "nsenter: " fmt ": %m", ##__VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while(0)
|
||||
|
||||
static int write_file(char *data, size_t data_len, char *pathfmt, ...)
|
||||
|
@ -352,6 +370,23 @@ static int initpipe(void)
|
|||
return pipenum;
|
||||
}
|
||||
|
||||
static void setup_logpipe(void)
|
||||
{
|
||||
char *logpipe, *endptr;
|
||||
|
||||
logpipe = getenv("_LIBCONTAINER_LOGPIPE");
|
||||
if (logpipe == NULL || *logpipe == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
logfd = strtol(logpipe, &endptr, 10);
|
||||
if (logpipe == endptr || *endptr != '\0') {
|
||||
fprintf(stderr, "unable to parse _LIBCONTAINER_LOGPIPE, value: %s\n", logpipe);
|
||||
/* It is too early to use bail */
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the clone(2) flag for a namespace, given the name of a namespace. */
|
||||
static int nsflag(char *name)
|
||||
{
|
||||
|
@ -544,6 +579,12 @@ void nsexec(void)
|
|||
int sync_child_pipe[2], sync_grandchild_pipe[2];
|
||||
struct nlconfig_t config = { 0 };
|
||||
|
||||
/*
|
||||
* Setup a pipe to send logs to the parent. This should happen
|
||||
* first, because bail will use that pipe.
|
||||
*/
|
||||
setup_logpipe();
|
||||
|
||||
/*
|
||||
* If we don't have an init pipe, just return to the go routine.
|
||||
* We'll only get an init pipe for start or exec.
|
||||
|
@ -560,6 +601,8 @@ void nsexec(void)
|
|||
if (ensure_cloned_binary() < 0)
|
||||
bail("could not ensure we are a cloned binary");
|
||||
|
||||
write_log(DEBUG, "nsexec started");
|
||||
|
||||
/* Parse all of the netlink configuration. */
|
||||
nl_parse(pipenum, &config);
|
||||
|
||||
|
@ -676,7 +719,6 @@ void nsexec(void)
|
|||
*/
|
||||
while (!ready) {
|
||||
enum sync_t s;
|
||||
int ret;
|
||||
|
||||
syncfd = sync_child_pipe[1];
|
||||
close(sync_child_pipe[0]);
|
||||
|
@ -685,12 +727,6 @@ void nsexec(void)
|
|||
bail("failed to sync with child: next state");
|
||||
|
||||
switch (s) {
|
||||
case SYNC_ERR:
|
||||
/* We have to mirror the error code of the child. */
|
||||
if (read(syncfd, &ret, sizeof(ret)) != sizeof(ret))
|
||||
bail("failed to sync with child: read(error code)");
|
||||
|
||||
exit(ret);
|
||||
case SYNC_USERMAP_PLS:
|
||||
/*
|
||||
* Enable setgroups(2) if we've been asked to. But we also
|
||||
|
@ -759,7 +795,6 @@ void nsexec(void)
|
|||
ready = false;
|
||||
while (!ready) {
|
||||
enum sync_t s;
|
||||
int ret;
|
||||
|
||||
syncfd = sync_grandchild_pipe[1];
|
||||
close(sync_grandchild_pipe[0]);
|
||||
|
@ -774,12 +809,6 @@ void nsexec(void)
|
|||
bail("failed to sync with child: next state");
|
||||
|
||||
switch (s) {
|
||||
case SYNC_ERR:
|
||||
/* We have to mirror the error code of the child. */
|
||||
if (read(syncfd, &ret, sizeof(ret)) != sizeof(ret))
|
||||
bail("failed to sync with child: read(error code)");
|
||||
|
||||
exit(ret);
|
||||
case SYNC_CHILD_READY:
|
||||
ready = true;
|
||||
break;
|
||||
|
|
|
@ -76,6 +76,8 @@ type Process struct {
|
|||
Init bool
|
||||
|
||||
ops processOperations
|
||||
|
||||
LogLevel string
|
||||
}
|
||||
|
||||
// Wait waits for the process to exit.
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
||||
"github.com/opencontainers/runc/libcontainer/logs"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
|
||||
|
@ -47,12 +48,19 @@ type parentProcess interface {
|
|||
externalDescriptors() []string
|
||||
|
||||
setExternalDescriptors(fds []string)
|
||||
|
||||
forwardChildLogs()
|
||||
}
|
||||
|
||||
type filePair struct {
|
||||
parent *os.File
|
||||
child *os.File
|
||||
}
|
||||
|
||||
type setnsProcess struct {
|
||||
cmd *exec.Cmd
|
||||
parentPipe *os.File
|
||||
childPipe *os.File
|
||||
messageSockPair filePair
|
||||
logFilePair filePair
|
||||
cgroupPaths map[string]string
|
||||
rootlessCgroups bool
|
||||
intelRdtPath string
|
||||
|
@ -76,14 +84,16 @@ func (p *setnsProcess) signal(sig os.Signal) error {
|
|||
}
|
||||
|
||||
func (p *setnsProcess) start() (err error) {
|
||||
defer p.parentPipe.Close()
|
||||
defer p.messageSockPair.parent.Close()
|
||||
err = p.cmd.Start()
|
||||
p.childPipe.Close()
|
||||
// close the write-side of the pipes (controlled by child)
|
||||
p.messageSockPair.child.Close()
|
||||
p.logFilePair.child.Close()
|
||||
if err != nil {
|
||||
return newSystemErrorWithCause(err, "starting setns process")
|
||||
}
|
||||
if p.bootstrapData != nil {
|
||||
if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil {
|
||||
if _, err := io.Copy(p.messageSockPair.parent, p.bootstrapData); err != nil {
|
||||
return newSystemErrorWithCause(err, "copying bootstrap data to pipe")
|
||||
}
|
||||
}
|
||||
|
@ -109,11 +119,11 @@ func (p *setnsProcess) start() (err error) {
|
|||
if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil {
|
||||
return newSystemErrorWithCause(err, "setting rlimits for process")
|
||||
}
|
||||
if err := utils.WriteJSON(p.parentPipe, p.config); err != nil {
|
||||
if err := utils.WriteJSON(p.messageSockPair.parent, p.config); err != nil {
|
||||
return newSystemErrorWithCause(err, "writing config to pipe")
|
||||
}
|
||||
|
||||
ierr := parseSync(p.parentPipe, func(sync *syncT) error {
|
||||
ierr := parseSync(p.messageSockPair.parent, func(sync *syncT) error {
|
||||
switch sync.Type {
|
||||
case procReady:
|
||||
// This shouldn't happen.
|
||||
|
@ -126,7 +136,7 @@ func (p *setnsProcess) start() (err error) {
|
|||
}
|
||||
})
|
||||
|
||||
if err := unix.Shutdown(int(p.parentPipe.Fd()), unix.SHUT_WR); err != nil {
|
||||
if err := unix.Shutdown(int(p.messageSockPair.parent.Fd()), unix.SHUT_WR); err != nil {
|
||||
return newSystemErrorWithCause(err, "calling shutdown on init pipe")
|
||||
}
|
||||
// Must be done after Shutdown so the child will exit and we can wait for it.
|
||||
|
@ -152,16 +162,14 @@ func (p *setnsProcess) execSetns() error {
|
|||
return newSystemError(&exec.ExitError{ProcessState: status})
|
||||
}
|
||||
var pid *pid
|
||||
if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil {
|
||||
if err := json.NewDecoder(p.messageSockPair.parent).Decode(&pid); err != nil {
|
||||
p.cmd.Wait()
|
||||
return newSystemErrorWithCause(err, "reading pid from init pipe")
|
||||
}
|
||||
|
||||
// Clean up the zombie parent process
|
||||
firstChildProcess, err := os.FindProcess(pid.PidFirstChild)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// On Unix systems FindProcess always succeeds.
|
||||
firstChildProcess, _ := os.FindProcess(pid.PidFirstChild)
|
||||
|
||||
// Ignore the error in case the child has already been reaped for any reason
|
||||
_, _ = firstChildProcess.Wait()
|
||||
|
@ -207,10 +215,14 @@ func (p *setnsProcess) setExternalDescriptors(newFds []string) {
|
|||
p.fds = newFds
|
||||
}
|
||||
|
||||
func (p *setnsProcess) forwardChildLogs() {
|
||||
go logs.ForwardLogs(p.logFilePair.parent)
|
||||
}
|
||||
|
||||
type initProcess struct {
|
||||
cmd *exec.Cmd
|
||||
parentPipe *os.File
|
||||
childPipe *os.File
|
||||
messageSockPair filePair
|
||||
logFilePair filePair
|
||||
config *initConfig
|
||||
manager cgroups.Manager
|
||||
intelRdtManager intelrdt.Manager
|
||||
|
@ -232,10 +244,18 @@ func (p *initProcess) externalDescriptors() []string {
|
|||
// getChildPid receives the final child's pid over the provided pipe.
|
||||
func (p *initProcess) getChildPid() (int, error) {
|
||||
var pid pid
|
||||
if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil {
|
||||
if err := json.NewDecoder(p.messageSockPair.parent).Decode(&pid); err != nil {
|
||||
p.cmd.Wait()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Clean up the zombie parent process
|
||||
// On Unix systems FindProcess always succeeds.
|
||||
firstChildProcess, _ := os.FindProcess(pid.PidFirstChild)
|
||||
|
||||
// Ignore the error in case the child has already been reaped for any reason
|
||||
_, _ = firstChildProcess.Wait()
|
||||
|
||||
return pid.Pid, nil
|
||||
}
|
||||
|
||||
|
@ -260,10 +280,12 @@ func (p *initProcess) waitForChildExit(childPid int) error {
|
|||
}
|
||||
|
||||
func (p *initProcess) start() error {
|
||||
defer p.parentPipe.Close()
|
||||
defer p.messageSockPair.parent.Close()
|
||||
err := p.cmd.Start()
|
||||
p.process.ops = p
|
||||
p.childPipe.Close()
|
||||
// close the write-side of the pipes (controlled by child)
|
||||
p.messageSockPair.child.Close()
|
||||
p.logFilePair.child.Close()
|
||||
if err != nil {
|
||||
p.process.ops = nil
|
||||
return newSystemErrorWithCause(err, "starting init process command")
|
||||
|
@ -289,7 +311,7 @@ func (p *initProcess) start() error {
|
|||
}
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil {
|
||||
if _, err := io.Copy(p.messageSockPair.parent, p.bootstrapData); err != nil {
|
||||
return newSystemErrorWithCause(err, "copying bootstrap data to pipe")
|
||||
}
|
||||
childPid, err := p.getChildPid()
|
||||
|
@ -317,7 +339,7 @@ func (p *initProcess) start() error {
|
|||
}
|
||||
// Now it's time to setup cgroup namesapce
|
||||
if p.config.Config.Namespaces.Contains(configs.NEWCGROUP) && p.config.Config.Namespaces.PathOf(configs.NEWCGROUP) == "" {
|
||||
if _, err := p.parentPipe.Write([]byte{createCgroupns}); err != nil {
|
||||
if _, err := p.messageSockPair.parent.Write([]byte{createCgroupns}); err != nil {
|
||||
return newSystemErrorWithCause(err, "sending synchronization value to init process")
|
||||
}
|
||||
}
|
||||
|
@ -331,6 +353,9 @@ func (p *initProcess) start() error {
|
|||
if err != nil {
|
||||
// TODO: should not be the responsibility to call here
|
||||
p.manager.Destroy()
|
||||
if p.intelRdtManager != nil {
|
||||
p.intelRdtManager.Destroy()
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err := p.createNetworkInterfaces(); err != nil {
|
||||
|
@ -344,7 +369,7 @@ func (p *initProcess) start() error {
|
|||
sentResume bool
|
||||
)
|
||||
|
||||
ierr := parseSync(p.parentPipe, func(sync *syncT) error {
|
||||
ierr := parseSync(p.messageSockPair.parent, func(sync *syncT) error {
|
||||
switch sync.Type {
|
||||
case procReady:
|
||||
// set rlimits, this has to be done here because we lose permissions
|
||||
|
@ -380,7 +405,7 @@ func (p *initProcess) start() error {
|
|||
}
|
||||
}
|
||||
// Sync with child.
|
||||
if err := writeSync(p.parentPipe, procRun); err != nil {
|
||||
if err := writeSync(p.messageSockPair.parent, procRun); err != nil {
|
||||
return newSystemErrorWithCause(err, "writing syncT 'run'")
|
||||
}
|
||||
sentRun = true
|
||||
|
@ -409,7 +434,7 @@ func (p *initProcess) start() error {
|
|||
}
|
||||
}
|
||||
// Sync with child.
|
||||
if err := writeSync(p.parentPipe, procResume); err != nil {
|
||||
if err := writeSync(p.messageSockPair.parent, procResume); err != nil {
|
||||
return newSystemErrorWithCause(err, "writing syncT 'resume'")
|
||||
}
|
||||
sentResume = true
|
||||
|
@ -426,7 +451,7 @@ func (p *initProcess) start() error {
|
|||
if p.config.Config.Namespaces.Contains(configs.NEWNS) && !sentResume {
|
||||
return newSystemError(fmt.Errorf("could not synchronise after executing prestart hooks with container process"))
|
||||
}
|
||||
if err := unix.Shutdown(int(p.parentPipe.Fd()), unix.SHUT_WR); err != nil {
|
||||
if err := unix.Shutdown(int(p.messageSockPair.parent.Fd()), unix.SHUT_WR); err != nil {
|
||||
return newSystemErrorWithCause(err, "shutting down init pipe")
|
||||
}
|
||||
|
||||
|
@ -470,7 +495,7 @@ func (p *initProcess) sendConfig() error {
|
|||
// send the config to the container's init process, we don't use JSON Encode
|
||||
// here because there might be a problem in JSON decoder in some cases, see:
|
||||
// https://github.com/docker/docker/issues/14203#issuecomment-174177790
|
||||
return utils.WriteJSON(p.parentPipe, p.config)
|
||||
return utils.WriteJSON(p.messageSockPair.parent, p.config)
|
||||
}
|
||||
|
||||
func (p *initProcess) createNetworkInterfaces() error {
|
||||
|
@ -502,6 +527,10 @@ func (p *initProcess) setExternalDescriptors(newFds []string) {
|
|||
p.fds = newFds
|
||||
}
|
||||
|
||||
func (p *initProcess) forwardChildLogs() {
|
||||
go logs.ForwardLogs(p.logFilePair.parent)
|
||||
}
|
||||
|
||||
func getPipeFds(pid int) ([]string, error) {
|
||||
fds := make([]string, 3)
|
||||
|
||||
|
|
|
@ -76,6 +76,9 @@ func (p *restoredProcess) setExternalDescriptors(newFds []string) {
|
|||
p.fds = newFds
|
||||
}
|
||||
|
||||
func (p *restoredProcess) forwardChildLogs() {
|
||||
}
|
||||
|
||||
// nonChildProcess represents a process where the calling process is not
|
||||
// the parent process. This process is created when a factory loads a container from
|
||||
// a persisted state.
|
||||
|
@ -120,3 +123,6 @@ func (p *nonChildProcess) externalDescriptors() []string {
|
|||
func (p *nonChildProcess) setExternalDescriptors(newFds []string) {
|
||||
p.fds = newFds
|
||||
}
|
||||
|
||||
func (p *nonChildProcess) forwardChildLogs() {
|
||||
}
|
||||
|
|
|
@ -34,6 +34,10 @@ func (l *linuxSetnsInit) Init() error {
|
|||
defer runtime.UnlockOSThread()
|
||||
|
||||
if !l.config.Config.NoNewKeyring {
|
||||
if err := label.SetKeyLabel(l.config.ProcessLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
defer label.SetKeyLabel("")
|
||||
// Do not inherit the parent's session keyring.
|
||||
if _, err := keys.JoinSessionKeyring(l.getSessionRingName()); err != nil {
|
||||
// Same justification as in standart_init_linux.go as to why we
|
||||
|
|
|
@ -48,6 +48,10 @@ func (l *linuxStandardInit) Init() error {
|
|||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if !l.config.Config.NoNewKeyring {
|
||||
if err := label.SetKeyLabel(l.config.ProcessLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
defer label.SetKeyLabel("")
|
||||
ringname, keepperms, newperms := l.getSessionRingParams()
|
||||
|
||||
// Do not inherit the parent's session keyring.
|
||||
|
|
|
@ -9,7 +9,7 @@ func InitLabels(options []string) (string, string, error) {
|
|||
return "", "", nil
|
||||
}
|
||||
|
||||
func GetROMountLabel() string {
|
||||
func ROMountLabel() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,27 @@ func SetProcessLabel(processLabel string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetFileLabel(path string) (string, error) {
|
||||
func ProcessLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func SetSocketLabel(processLabel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SocketLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func SetKeyLabel(processLabel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func KeyLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func FileLabel(path string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
@ -41,13 +61,18 @@ func Relabel(path string, fileLabel string, shared bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetPidLabel(pid int) (string, error) {
|
||||
func PidLabel(pid int) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func Init() {
|
||||
}
|
||||
|
||||
// ClearLabels clears all reserved labels
|
||||
func ClearLabels() {
|
||||
return
|
||||
}
|
||||
|
||||
func ReserveLabel(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -58,8 +83,8 @@ func ReleaseLabel(label string) error {
|
|||
|
||||
// DupSecOpt takes a process label and returns security options that
|
||||
// can be used to set duplicate labels on future container processes
|
||||
func DupSecOpt(src string) []string {
|
||||
return nil
|
||||
func DupSecOpt(src string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DisableSecOpt returns a security opt that can disable labeling
|
||||
|
|
|
@ -4,6 +4,8 @@ package label
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
|
@ -24,17 +26,29 @@ var ErrIncompatibleLabel = fmt.Errorf("Bad SELinux option z and Z can not be use
|
|||
// the container. A list of options can be passed into this function to alter
|
||||
// the labels. The labels returned will include a random MCS String, that is
|
||||
// guaranteed to be unique.
|
||||
func InitLabels(options []string) (string, string, error) {
|
||||
func InitLabels(options []string) (plabel string, mlabel string, Err error) {
|
||||
if !selinux.GetEnabled() {
|
||||
return "", "", nil
|
||||
}
|
||||
processLabel, mountLabel := selinux.ContainerLabels()
|
||||
if processLabel != "" {
|
||||
pcon := selinux.NewContext(processLabel)
|
||||
mcon := selinux.NewContext(mountLabel)
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
ReleaseLabel(mountLabel)
|
||||
}
|
||||
}()
|
||||
pcon, err := selinux.NewContext(processLabel)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
mcon, err := selinux.NewContext(mountLabel)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
for _, opt := range options {
|
||||
if opt == "disable" {
|
||||
return "", "", nil
|
||||
return "", mountLabel, nil
|
||||
}
|
||||
if i := strings.Index(opt, ":"); i == -1 {
|
||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type' followed by ':' and a value", opt)
|
||||
|
@ -49,8 +63,10 @@ func InitLabels(options []string) (string, string, error) {
|
|||
mcon[con[0]] = con[1]
|
||||
}
|
||||
}
|
||||
_ = ReleaseLabel(processLabel)
|
||||
processLabel = pcon.Get()
|
||||
mountLabel = mcon.Get()
|
||||
_ = ReserveLabel(processLabel)
|
||||
}
|
||||
return processLabel, mountLabel, nil
|
||||
}
|
||||
|
@ -85,12 +101,31 @@ func FormatMountLabel(src, mountLabel string) string {
|
|||
// SetProcessLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next program executed by the current process.
|
||||
func SetProcessLabel(processLabel string) error {
|
||||
if processLabel == "" {
|
||||
return nil
|
||||
}
|
||||
return selinux.SetExecLabel(processLabel)
|
||||
}
|
||||
|
||||
// SetSocketLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next socket that gets created
|
||||
func SetSocketLabel(processLabel string) error {
|
||||
return selinux.SetSocketLabel(processLabel)
|
||||
}
|
||||
|
||||
// SocketLabel retrieves the current default socket label setting
|
||||
func SocketLabel() (string, error) {
|
||||
return selinux.SocketLabel()
|
||||
}
|
||||
|
||||
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next kernel keyring that gets created
|
||||
func SetKeyLabel(processLabel string) error {
|
||||
return selinux.SetKeyLabel(processLabel)
|
||||
}
|
||||
|
||||
// KeyLabel retrieves the current default kernel keyring label setting
|
||||
func KeyLabel() (string, error) {
|
||||
return selinux.KeyLabel()
|
||||
}
|
||||
|
||||
// ProcessLabel returns the process label that the kernel will assign
|
||||
// to the next program executed by the current process. If "" is returned
|
||||
// this indicates that the default labeling will happen for the process.
|
||||
|
@ -98,7 +133,7 @@ func ProcessLabel() (string, error) {
|
|||
return selinux.ExecLabel()
|
||||
}
|
||||
|
||||
// GetFileLabel returns the label for specified path
|
||||
// FileLabel returns the label for specified path
|
||||
func FileLabel(path string) (string, error) {
|
||||
return selinux.FileLabel(path)
|
||||
}
|
||||
|
@ -131,13 +166,56 @@ func Relabel(path string, fileLabel string, shared bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
exclude_paths := map[string]bool{"/": true, "/usr": true, "/etc": true}
|
||||
exclude_paths := map[string]bool{
|
||||
"/": true,
|
||||
"/bin": true,
|
||||
"/boot": true,
|
||||
"/dev": true,
|
||||
"/etc": true,
|
||||
"/etc/passwd": true,
|
||||
"/etc/pki": true,
|
||||
"/etc/shadow": true,
|
||||
"/home": true,
|
||||
"/lib": true,
|
||||
"/lib64": true,
|
||||
"/media": true,
|
||||
"/opt": true,
|
||||
"/proc": true,
|
||||
"/root": true,
|
||||
"/run": true,
|
||||
"/sbin": true,
|
||||
"/srv": true,
|
||||
"/sys": true,
|
||||
"/tmp": true,
|
||||
"/usr": true,
|
||||
"/var": true,
|
||||
"/var/lib": true,
|
||||
"/var/log": true,
|
||||
}
|
||||
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
exclude_paths[home] = true
|
||||
}
|
||||
|
||||
if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" {
|
||||
if usr, err := user.Lookup(sudoUser); err == nil {
|
||||
exclude_paths[usr.HomeDir] = true
|
||||
}
|
||||
}
|
||||
|
||||
if path != "/" {
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
}
|
||||
if exclude_paths[path] {
|
||||
return fmt.Errorf("SELinux relabeling of %s is not allowed", path)
|
||||
}
|
||||
|
||||
if shared {
|
||||
c := selinux.NewContext(fileLabel)
|
||||
c, err := selinux.NewContext(fileLabel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c["level"] = "s0"
|
||||
fileLabel = c.Get()
|
||||
}
|
||||
|
@ -157,6 +235,11 @@ func Init() {
|
|||
selinux.GetEnabled()
|
||||
}
|
||||
|
||||
// ClearLabels will clear all reserved labels
|
||||
func ClearLabels() {
|
||||
selinux.ClearLabels()
|
||||
}
|
||||
|
||||
// ReserveLabel will record the fact that the MCS label has already been used.
|
||||
// This will prevent InitLabels from using the MCS label in a newly created
|
||||
// container
|
||||
|
@ -175,7 +258,7 @@ func ReleaseLabel(label string) error {
|
|||
|
||||
// DupSecOpt takes a process label and returns security options that
|
||||
// can be used to set duplicate labels on future container processes
|
||||
func DupSecOpt(src string) []string {
|
||||
func DupSecOpt(src string) ([]string, error) {
|
||||
return selinux.DupSecOpt(src)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
// +build linux
|
||||
// +build selinux,linux
|
||||
|
||||
package selinux
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
@ -23,14 +26,16 @@ const (
|
|||
// Permissive constant to indicate SELinux is in permissive mode
|
||||
Permissive = 0
|
||||
// Disabled constant to indicate SELinux is disabled
|
||||
Disabled = -1
|
||||
Disabled = -1
|
||||
|
||||
selinuxDir = "/etc/selinux/"
|
||||
selinuxConfig = selinuxDir + "config"
|
||||
selinuxfsMount = "/sys/fs/selinux"
|
||||
selinuxTypeTag = "SELINUXTYPE"
|
||||
selinuxTag = "SELINUX"
|
||||
selinuxPath = "/sys/fs/selinux"
|
||||
xattrNameSelinux = "security.selinux"
|
||||
stRdOnly = 0x01
|
||||
selinuxfsMagic = 0xf97cff8c
|
||||
)
|
||||
|
||||
type selinuxState struct {
|
||||
|
@ -43,7 +48,15 @@ type selinuxState struct {
|
|||
}
|
||||
|
||||
var (
|
||||
// ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS.
|
||||
ErrMCSAlreadyExists = errors.New("MCS label already exists")
|
||||
// ErrEmptyPath is returned when an empty path has been specified.
|
||||
ErrEmptyPath = errors.New("empty path")
|
||||
// InvalidLabel is returned when an invalid label is specified.
|
||||
InvalidLabel = errors.New("Invalid Label")
|
||||
|
||||
assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
|
||||
roFileLabel string
|
||||
state = selinuxState{
|
||||
mcsList: make(map[string]bool),
|
||||
}
|
||||
|
@ -91,6 +104,83 @@ func (s *selinuxState) setSELinuxfs(selinuxfs string) string {
|
|||
return s.selinuxfs
|
||||
}
|
||||
|
||||
func verifySELinuxfsMount(mnt string) bool {
|
||||
var buf syscall.Statfs_t
|
||||
for {
|
||||
err := syscall.Statfs(mnt, &buf)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err == syscall.EAGAIN {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
if uint32(buf.Type) != uint32(selinuxfsMagic) {
|
||||
return false
|
||||
}
|
||||
if (buf.Flags & stRdOnly) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func findSELinuxfs() string {
|
||||
// fast path: check the default mount first
|
||||
if verifySELinuxfsMount(selinuxfsMount) {
|
||||
return selinuxfsMount
|
||||
}
|
||||
|
||||
// check if selinuxfs is available before going the slow path
|
||||
fs, err := ioutil.ReadFile("/proc/filesystems")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if !bytes.Contains(fs, []byte("\tselinuxfs\n")) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// slow path: try to find among the mounts
|
||||
f, err := os.Open("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for {
|
||||
mnt := findSELinuxfsMount(scanner)
|
||||
if mnt == "" { // error or not found
|
||||
return ""
|
||||
}
|
||||
if verifySELinuxfsMount(mnt) {
|
||||
return mnt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findSELinuxfsMount returns a next selinuxfs mount point found,
|
||||
// if there is one, or an empty string in case of EOF or error.
|
||||
func findSELinuxfsMount(s *bufio.Scanner) string {
|
||||
for s.Scan() {
|
||||
txt := s.Text()
|
||||
// The first field after - is fs type.
|
||||
// Safe as spaces in mountpoints are encoded as \040
|
||||
if !strings.Contains(txt, " - selinuxfs ") {
|
||||
continue
|
||||
}
|
||||
const mPos = 5 // mount point is 5th field
|
||||
fields := strings.SplitN(txt, " ", mPos+1)
|
||||
if len(fields) < mPos+1 {
|
||||
continue
|
||||
}
|
||||
return fields[mPos-1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *selinuxState) getSELinuxfs() string {
|
||||
s.Lock()
|
||||
selinuxfs := s.selinuxfs
|
||||
|
@ -100,40 +190,7 @@ func (s *selinuxState) getSELinuxfs() string {
|
|||
return selinuxfs
|
||||
}
|
||||
|
||||
selinuxfs = ""
|
||||
f, err := os.Open("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
return selinuxfs
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
txt := scanner.Text()
|
||||
// Safe as mountinfo encodes mountpoints with spaces as \040.
|
||||
sepIdx := strings.Index(txt, " - ")
|
||||
if sepIdx == -1 {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(txt[sepIdx:], "selinuxfs") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(txt, " ")
|
||||
if len(fields) < 5 {
|
||||
continue
|
||||
}
|
||||
selinuxfs = fields[4]
|
||||
break
|
||||
}
|
||||
|
||||
if selinuxfs != "" {
|
||||
var buf syscall.Statfs_t
|
||||
syscall.Statfs(selinuxfs, &buf)
|
||||
if (buf.Flags & stRdOnly) == 1 {
|
||||
selinuxfs = ""
|
||||
}
|
||||
}
|
||||
return s.setSELinuxfs(selinuxfs)
|
||||
return s.setSELinuxfs(findSELinuxfs())
|
||||
}
|
||||
|
||||
// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
|
||||
|
@ -150,7 +207,7 @@ func GetEnabled() bool {
|
|||
return state.getEnabled()
|
||||
}
|
||||
|
||||
func readConfig(target string) (value string) {
|
||||
func readConfig(target string) string {
|
||||
var (
|
||||
val, key string
|
||||
bufin *bufio.Reader
|
||||
|
@ -192,30 +249,42 @@ func readConfig(target string) (value string) {
|
|||
}
|
||||
|
||||
func getSELinuxPolicyRoot() string {
|
||||
return selinuxDir + readConfig(selinuxTypeTag)
|
||||
return filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
|
||||
}
|
||||
|
||||
func readCon(name string) (string, error) {
|
||||
var val string
|
||||
func readCon(fpath string) (string, error) {
|
||||
if fpath == "" {
|
||||
return "", ErrEmptyPath
|
||||
}
|
||||
|
||||
in, err := os.Open(name)
|
||||
in, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
_, err = fmt.Fscanf(in, "%s", &val)
|
||||
return val, err
|
||||
var retval string
|
||||
if _, err := fmt.Fscanf(in, "%s", &retval); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Trim(retval, "\x00"), nil
|
||||
}
|
||||
|
||||
// SetFileLabel sets the SELinux label for this path or returns an error.
|
||||
func SetFileLabel(path string, label string) error {
|
||||
return lsetxattr(path, xattrNameSelinux, []byte(label), 0)
|
||||
func SetFileLabel(fpath string, label string) error {
|
||||
if fpath == "" {
|
||||
return ErrEmptyPath
|
||||
}
|
||||
return lsetxattr(fpath, xattrNameSelinux, []byte(label), 0)
|
||||
}
|
||||
|
||||
// Filecon returns the SELinux label for this path or returns an error.
|
||||
func FileLabel(path string) (string, error) {
|
||||
label, err := lgetxattr(path, xattrNameSelinux)
|
||||
// FileLabel returns the SELinux label for this path or returns an error.
|
||||
func FileLabel(fpath string) (string, error) {
|
||||
if fpath == "" {
|
||||
return "", ErrEmptyPath
|
||||
}
|
||||
|
||||
label, err := lgetxattr(fpath, xattrNameSelinux)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -260,8 +329,17 @@ func ExecLabel() (string, error) {
|
|||
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()))
|
||||
}
|
||||
|
||||
func writeCon(name string, val string) error {
|
||||
out, err := os.OpenFile(name, os.O_WRONLY, 0)
|
||||
func writeCon(fpath string, val string) error {
|
||||
if fpath == "" {
|
||||
return ErrEmptyPath
|
||||
}
|
||||
if val == "" {
|
||||
if !GetEnabled() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
out, err := os.OpenFile(fpath, os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -275,6 +353,37 @@ func writeCon(name string, val string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
CanonicalizeContext takes a context string and writes it to the kernel
|
||||
the function then returns the context that the kernel will use. This function
|
||||
can be used to see if two contexts are equivalent
|
||||
*/
|
||||
func CanonicalizeContext(val string) (string, error) {
|
||||
return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val)
|
||||
}
|
||||
|
||||
func readWriteCon(fpath string, val string) (string, error) {
|
||||
if fpath == "" {
|
||||
return "", ErrEmptyPath
|
||||
}
|
||||
f, err := os.OpenFile(fpath, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write([]byte(val))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var retval string
|
||||
if _, err := fmt.Fscanf(f, "%s", &retval); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Trim(retval, "\x00"), nil
|
||||
}
|
||||
|
||||
/*
|
||||
SetExecLabel sets the SELinux label that the kernel will use for any programs
|
||||
that are executed by the current process thread, or an error.
|
||||
|
@ -283,35 +392,81 @@ func SetExecLabel(label string) error {
|
|||
return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), label)
|
||||
}
|
||||
|
||||
// SetSocketLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next socket that gets created
|
||||
func SetSocketLabel(label string) error {
|
||||
return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/sockcreate", syscall.Gettid()), label)
|
||||
}
|
||||
|
||||
// SocketLabel retrieves the current socket label setting
|
||||
func SocketLabel() (string, error) {
|
||||
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/sockcreate", syscall.Gettid()))
|
||||
}
|
||||
|
||||
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next kernel keyring that gets created
|
||||
func SetKeyLabel(label string) error {
|
||||
err := writeCon("/proc/self/attr/keycreate", label)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if label == "" && os.IsPermission(err) && !GetEnabled() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// KeyLabel retrieves the current kernel keyring label setting
|
||||
func KeyLabel() (string, error) {
|
||||
return readCon("/proc/self/attr/keycreate")
|
||||
}
|
||||
|
||||
// Get returns the Context as a string
|
||||
func (c Context) Get() string {
|
||||
return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
|
||||
if c["level"] != "" {
|
||||
return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", c["user"], c["role"], c["type"])
|
||||
}
|
||||
|
||||
// NewContext creates a new Context struct from the specified label
|
||||
func NewContext(label string) Context {
|
||||
func NewContext(label string) (Context, error) {
|
||||
c := make(Context)
|
||||
|
||||
if len(label) != 0 {
|
||||
con := strings.SplitN(label, ":", 4)
|
||||
if len(con) < 3 {
|
||||
return c, InvalidLabel
|
||||
}
|
||||
c["user"] = con[0]
|
||||
c["role"] = con[1]
|
||||
c["type"] = con[2]
|
||||
c["level"] = con[3]
|
||||
if len(con) > 3 {
|
||||
c["level"] = con[3]
|
||||
}
|
||||
}
|
||||
return c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ClearLabels clears all reserved labels
|
||||
func ClearLabels() {
|
||||
state.Lock()
|
||||
state.mcsList = make(map[string]bool)
|
||||
state.Unlock()
|
||||
}
|
||||
|
||||
// ReserveLabel reserves the MLS/MCS level component of the specified label
|
||||
func ReserveLabel(label string) {
|
||||
if len(label) != 0 {
|
||||
con := strings.SplitN(label, ":", 4)
|
||||
mcsAdd(con[3])
|
||||
if len(con) > 3 {
|
||||
mcsAdd(con[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func selinuxEnforcePath() string {
|
||||
return fmt.Sprintf("%s/enforce", selinuxPath)
|
||||
return fmt.Sprintf("%s/enforce", getSelinuxMountPoint())
|
||||
}
|
||||
|
||||
// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled
|
||||
|
@ -331,7 +486,7 @@ func EnforceMode() int {
|
|||
}
|
||||
|
||||
/*
|
||||
SetEnforce sets the current SELinux mode Enforcing, Permissive.
|
||||
SetEnforceMode sets the current SELinux mode Enforcing, Permissive.
|
||||
Disabled is not valid, since this needs to be set at boot time.
|
||||
*/
|
||||
func SetEnforceMode(mode int) error {
|
||||
|
@ -354,16 +509,22 @@ func DefaultEnforceMode() int {
|
|||
}
|
||||
|
||||
func mcsAdd(mcs string) error {
|
||||
if mcs == "" {
|
||||
return nil
|
||||
}
|
||||
state.Lock()
|
||||
defer state.Unlock()
|
||||
if state.mcsList[mcs] {
|
||||
return fmt.Errorf("MCS Label already exists")
|
||||
return ErrMCSAlreadyExists
|
||||
}
|
||||
state.mcsList[mcs] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func mcsDelete(mcs string) {
|
||||
if mcs == "" {
|
||||
return
|
||||
}
|
||||
state.Lock()
|
||||
defer state.Unlock()
|
||||
state.mcsList[mcs] = false
|
||||
|
@ -424,14 +585,14 @@ Allowing it to be used by another process.
|
|||
func ReleaseLabel(label string) {
|
||||
if len(label) != 0 {
|
||||
con := strings.SplitN(label, ":", 4)
|
||||
mcsDelete(con[3])
|
||||
if len(con) > 3 {
|
||||
mcsDelete(con[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var roFileLabel string
|
||||
|
||||
// ROFileLabel returns the specified SELinux readonly file label
|
||||
func ROFileLabel() (fileLabel string) {
|
||||
func ROFileLabel() string {
|
||||
return roFileLabel
|
||||
}
|
||||
|
||||
|
@ -497,23 +658,25 @@ func ContainerLabels() (processLabel string, fileLabel string) {
|
|||
roFileLabel = fileLabel
|
||||
}
|
||||
exit:
|
||||
mcs := uniqMcs(1024)
|
||||
scon := NewContext(processLabel)
|
||||
scon["level"] = mcs
|
||||
processLabel = scon.Get()
|
||||
scon = NewContext(fileLabel)
|
||||
scon["level"] = mcs
|
||||
fileLabel = scon.Get()
|
||||
scon, _ := NewContext(processLabel)
|
||||
if scon["level"] != "" {
|
||||
mcs := uniqMcs(1024)
|
||||
scon["level"] = mcs
|
||||
processLabel = scon.Get()
|
||||
scon, _ = NewContext(fileLabel)
|
||||
scon["level"] = mcs
|
||||
fileLabel = scon.Get()
|
||||
}
|
||||
return processLabel, fileLabel
|
||||
}
|
||||
|
||||
// SecurityCheckContext validates that the SELinux label is understood by the kernel
|
||||
func SecurityCheckContext(val string) error {
|
||||
return writeCon(fmt.Sprintf("%s.context", selinuxPath), val)
|
||||
return writeCon(fmt.Sprintf("%s/context", getSelinuxMountPoint()), val)
|
||||
}
|
||||
|
||||
/*
|
||||
CopyLevel returns a label with the MLS/MCS level from src label replaces on
|
||||
CopyLevel returns a label with the MLS/MCS level from src label replaced on
|
||||
the dest label.
|
||||
*/
|
||||
func CopyLevel(src, dest string) (string, error) {
|
||||
|
@ -526,8 +689,14 @@ func CopyLevel(src, dest string) (string, error) {
|
|||
if err := SecurityCheckContext(dest); err != nil {
|
||||
return "", err
|
||||
}
|
||||
scon := NewContext(src)
|
||||
tcon := NewContext(dest)
|
||||
scon, err := NewContext(src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tcon, err := NewContext(dest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
mcsDelete(tcon["level"])
|
||||
mcsAdd(scon["level"])
|
||||
tcon["level"] = scon["level"]
|
||||
|
@ -536,20 +705,26 @@ func CopyLevel(src, dest string) (string, error) {
|
|||
|
||||
// Prevent users from relabing system files
|
||||
func badPrefix(fpath string) error {
|
||||
var badprefixes = []string{"/usr"}
|
||||
if fpath == "" {
|
||||
return ErrEmptyPath
|
||||
}
|
||||
|
||||
for _, prefix := range badprefixes {
|
||||
if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) {
|
||||
badPrefixes := []string{"/usr"}
|
||||
for _, prefix := range badPrefixes {
|
||||
if strings.HasPrefix(fpath, prefix) {
|
||||
return fmt.Errorf("relabeling content in %s is not allowed", prefix)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Chcon changes the fpath file object to the SELinux label label.
|
||||
// If the fpath is a directory and recurse is true Chcon will walk the
|
||||
// directory tree setting the label
|
||||
// Chcon changes the `fpath` file object to the SELinux label `label`.
|
||||
// If `fpath` is a directory and `recurse`` is true, Chcon will walk the
|
||||
// directory tree setting the label.
|
||||
func Chcon(fpath string, label string, recurse bool) error {
|
||||
if fpath == "" {
|
||||
return ErrEmptyPath
|
||||
}
|
||||
if label == "" {
|
||||
return nil
|
||||
}
|
||||
|
@ -557,7 +732,11 @@ func Chcon(fpath string, label string, recurse bool) error {
|
|||
return err
|
||||
}
|
||||
callback := func(p string, info os.FileInfo, err error) error {
|
||||
return SetFileLabel(p, label)
|
||||
e := SetFileLabel(p, label)
|
||||
if os.IsNotExist(e) {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
if recurse {
|
||||
|
@ -568,26 +747,34 @@ func Chcon(fpath string, label string, recurse bool) error {
|
|||
}
|
||||
|
||||
// DupSecOpt takes an SELinux process label and returns security options that
|
||||
// can will set the SELinux Type and Level for future container processes
|
||||
func DupSecOpt(src string) []string {
|
||||
// can be used to set the SELinux Type and Level for future container processes.
|
||||
func DupSecOpt(src string) ([]string, error) {
|
||||
if src == "" {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
con, err := NewContext(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
con := NewContext(src)
|
||||
if con["user"] == "" ||
|
||||
con["role"] == "" ||
|
||||
con["type"] == "" ||
|
||||
con["level"] == "" {
|
||||
return nil
|
||||
con["type"] == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return []string{"user:" + con["user"],
|
||||
dup := []string{"user:" + con["user"],
|
||||
"role:" + con["role"],
|
||||
"type:" + con["type"],
|
||||
"level:" + con["level"]}
|
||||
}
|
||||
|
||||
if con["level"] != "" {
|
||||
dup = append(dup, "level:"+con["level"])
|
||||
}
|
||||
|
||||
return dup, nil
|
||||
}
|
||||
|
||||
// DisableSecOpt returns a security opt that can be used to disabling SELinux
|
||||
// labeling support for future container processes
|
||||
// DisableSecOpt returns a security opt that can be used to disable SELinux
|
||||
// labeling support for future container processes.
|
||||
func DisableSecOpt() []string {
|
||||
return []string{"disable"}
|
||||
}
|
217
vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
generated
vendored
Normal file
217
vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
generated
vendored
Normal file
|
@ -0,0 +1,217 @@
|
|||
// +build !selinux
|
||||
|
||||
package selinux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// Enforcing constant indicate SELinux is in enforcing mode
|
||||
Enforcing = 1
|
||||
// Permissive constant to indicate SELinux is in permissive mode
|
||||
Permissive = 0
|
||||
// Disabled constant to indicate SELinux is disabled
|
||||
Disabled = -1
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS.
|
||||
ErrMCSAlreadyExists = errors.New("MCS label already exists")
|
||||
// ErrEmptyPath is returned when an empty path has been specified.
|
||||
ErrEmptyPath = errors.New("empty path")
|
||||
)
|
||||
|
||||
// Context is a representation of the SELinux label broken into 4 parts
|
||||
type Context map[string]string
|
||||
|
||||
// SetDisabled disables selinux support for the package
|
||||
func SetDisabled() {
|
||||
return
|
||||
}
|
||||
|
||||
// GetEnabled returns whether selinux is currently enabled.
|
||||
func GetEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetFileLabel sets the SELinux label for this path or returns an error.
|
||||
func SetFileLabel(fpath string, label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileLabel returns the SELinux label for this path or returns an error.
|
||||
func FileLabel(fpath string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
SetFSCreateLabel tells kernel the label to create all file system objects
|
||||
created by this task. Setting label="" to return to default.
|
||||
*/
|
||||
func SetFSCreateLabel(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
FSCreateLabel returns the default label the kernel which the kernel is using
|
||||
for file system objects created by this task. "" indicates default.
|
||||
*/
|
||||
func FSCreateLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// CurrentLabel returns the SELinux label of the current process thread, or an error.
|
||||
func CurrentLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// PidLabel returns the SELinux label of the given pid, or an error.
|
||||
func PidLabel(pid int) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
ExecLabel returns the SELinux label that the kernel will use for any programs
|
||||
that are executed by the current process thread, or an error.
|
||||
*/
|
||||
func ExecLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
CanonicalizeContext takes a context string and writes it to the kernel
|
||||
the function then returns the context that the kernel will use. This function
|
||||
can be used to see if two contexts are equivalent
|
||||
*/
|
||||
func CanonicalizeContext(val string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
SetExecLabel sets the SELinux label that the kernel will use for any programs
|
||||
that are executed by the current process thread, or an error.
|
||||
*/
|
||||
func SetExecLabel(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
SetSocketLabel sets the SELinux label that the kernel will use for any programs
|
||||
that are executed by the current process thread, or an error.
|
||||
*/
|
||||
func SetSocketLabel(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SocketLabel retrieves the current socket label setting
|
||||
func SocketLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next kernel keyring that gets created
|
||||
func SetKeyLabel(label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyLabel retrieves the current kernel keyring label setting
|
||||
func KeyLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Get returns the Context as a string
|
||||
func (c Context) Get() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// NewContext creates a new Context struct from the specified label
|
||||
func NewContext(label string) (Context, error) {
|
||||
c := make(Context)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ClearLabels clears all reserved MLS/MCS levels
|
||||
func ClearLabels() {
|
||||
return
|
||||
}
|
||||
|
||||
// ReserveLabel reserves the MLS/MCS level component of the specified label
|
||||
func ReserveLabel(label string) {
|
||||
return
|
||||
}
|
||||
|
||||
// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled
|
||||
func EnforceMode() int {
|
||||
return Disabled
|
||||
}
|
||||
|
||||
/*
|
||||
SetEnforceMode sets the current SELinux mode Enforcing, Permissive.
|
||||
Disabled is not valid, since this needs to be set at boot time.
|
||||
*/
|
||||
func SetEnforceMode(mode int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
DefaultEnforceMode returns the systems default SELinux mode Enforcing,
|
||||
Permissive or Disabled. Note this is is just the default at boot time.
|
||||
EnforceMode tells you the systems current mode.
|
||||
*/
|
||||
func DefaultEnforceMode() int {
|
||||
return Disabled
|
||||
}
|
||||
|
||||
/*
|
||||
ReleaseLabel will unreserve the MLS/MCS Level field of the specified label.
|
||||
Allowing it to be used by another process.
|
||||
*/
|
||||
func ReleaseLabel(label string) {
|
||||
return
|
||||
}
|
||||
|
||||
// ROFileLabel returns the specified SELinux readonly file label
|
||||
func ROFileLabel() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
/*
|
||||
ContainerLabels returns an allocated processLabel and fileLabel to be used for
|
||||
container labeling by the calling process.
|
||||
*/
|
||||
func ContainerLabels() (processLabel string, fileLabel string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// SecurityCheckContext validates that the SELinux label is understood by the kernel
|
||||
func SecurityCheckContext(val string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
CopyLevel returns a label with the MLS/MCS level from src label replaced on
|
||||
the dest label.
|
||||
*/
|
||||
func CopyLevel(src, dest string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Chcon changes the `fpath` file object to the SELinux label `label`.
|
||||
// If `fpath` is a directory and `recurse`` is true, Chcon will walk the
|
||||
// directory tree setting the label.
|
||||
func Chcon(fpath string, label string, recurse bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DupSecOpt takes an SELinux process label and returns security options that
|
||||
// can be used to set the SELinux Type and Level for future container processes.
|
||||
func DupSecOpt(src string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DisableSecOpt returns a security opt that can be used to disable SELinux
|
||||
// labeling support for future container processes.
|
||||
func DisableSecOpt() []string {
|
||||
return []string{"disable"}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// +build linux
|
||||
// +build selinux,linux
|
||||
|
||||
package selinux
|
||||
|
||||
|
|
|
@ -1,3 +1,80 @@
|
|||
# 1.4.2
|
||||
* Fixes build break for plan9, nacl, solaris
|
||||
# 1.4.1
|
||||
This new release introduces:
|
||||
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
||||
* Remove dependency on golang.org/x/crypto (#932, #943)
|
||||
|
||||
Fixes:
|
||||
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
||||
|
||||
# 1.4.0
|
||||
This new release introduces:
|
||||
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
||||
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter (#909, #911)
|
||||
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
||||
|
||||
Fixes:
|
||||
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
||||
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
||||
* Fix infinite recursion on unknown `Level.String()` (#907)
|
||||
* Fix race condition in `getCaller` (#916).
|
||||
|
||||
|
||||
# 1.3.0
|
||||
This new release introduces:
|
||||
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
||||
|
||||
Fixes:
|
||||
* Building prometheus node_exporter on AIX (#840)
|
||||
* Race condition in TextFormatter (#468)
|
||||
* Travis CI import path (#868)
|
||||
* Remove coloured output on Windows (#862)
|
||||
* Pointer to func as field in JSONFormatter (#870)
|
||||
* Properly marshal Levels (#873)
|
||||
|
||||
# 1.2.0
|
||||
This new release introduces:
|
||||
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
||||
* A new trace level named `Trace` whose level is below `Debug`
|
||||
* A configurable exit function to be called upon a Fatal trace
|
||||
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
||||
|
||||
# 1.1.1
|
||||
This is a bug fix release.
|
||||
* fix the build break on Solaris
|
||||
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
||||
|
||||
# 1.1.0
|
||||
This new release introduces:
|
||||
* several fixes:
|
||||
* a fix for a race condition on entry formatting
|
||||
* proper cleanup of previously used entries before putting them back in the pool
|
||||
* the extra new line at the end of message in text formatter has been removed
|
||||
* a new global public API to check if a level is activated: IsLevelEnabled
|
||||
* the following methods have been added to the Logger object
|
||||
* IsLevelEnabled
|
||||
* SetFormatter
|
||||
* SetOutput
|
||||
* ReplaceHooks
|
||||
* introduction of go module
|
||||
* an indent configuration for the json formatter
|
||||
* output colour support for windows
|
||||
* the field sort function is now configurable for text formatter
|
||||
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
||||
|
||||
# 1.0.6
|
||||
|
||||
This new release introduces:
|
||||
* a new api WithTime which allows to easily force the time of the log entry
|
||||
which is mostly useful for logger wrapper
|
||||
* a fix reverting the immutability of the entry given as parameter to the hooks
|
||||
a new configuration field of the json formatter in order to put all the fields
|
||||
in a nested dictionnary
|
||||
* a new SetOutput method in the Logger
|
||||
* a new configuration of the textformatter to configure the name of the default keys
|
||||
* a new configuration of the text formatter to disable the level truncation
|
||||
|
||||
# 1.0.5
|
||||
|
||||
* Fix hooks race (#707)
|
||||
|
|
|
@ -56,8 +56,39 @@ time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased
|
|||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
exit status 1
|
||||
```
|
||||
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
||||
|
||||
```go
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableColors: true,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
```
|
||||
|
||||
#### Logging Method Name
|
||||
|
||||
If you wish to add the calling method as a field, instruct the logger via:
|
||||
```go
|
||||
log.SetReportCaller(true)
|
||||
```
|
||||
This adds the caller as 'method' like so:
|
||||
|
||||
```json
|
||||
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
||||
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
||||
```
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
||||
```
|
||||
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
||||
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
||||
environment via benchmarks:
|
||||
```
|
||||
go test -bench=.*CallerTracing
|
||||
```
|
||||
|
||||
|
||||
#### Case-sensitivity
|
||||
|
||||
|
@ -156,7 +187,7 @@ func main() {
|
|||
log.Out = os.Stdout
|
||||
|
||||
// You could set this to any `io.Writer` such as a file
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
|
@ -246,9 +277,10 @@ A list of currently known of service hook can be found in this wiki [page](https
|
|||
|
||||
#### Level logging
|
||||
|
||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||
Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Trace("Something very low level.")
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
|
@ -329,9 +361,11 @@ The built-in logging formatters are:
|
|||
Third party logging formatters:
|
||||
|
||||
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
||||
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
|
||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
|
|
|
@ -51,9 +51,9 @@ func Exit(code int) {
|
|||
os.Exit(code)
|
||||
}
|
||||
|
||||
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
|
||||
// all handlers. The handlers will also be invoked when any Fatal log entry is
|
||||
// made.
|
||||
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
|
||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||
// any Fatal log entry is made.
|
||||
//
|
||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||
// message but also needs to gracefully shutdown. An example usecase could be
|
||||
|
@ -62,3 +62,15 @@ func Exit(code int) {
|
|||
func RegisterExitHandler(handler func()) {
|
||||
handlers = append(handlers, handler)
|
||||
}
|
||||
|
||||
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
|
||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||
// any Fatal log entry is made.
|
||||
//
|
||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||
// message but also needs to gracefully shutdown. An example usecase could be
|
||||
// closing database connections, or sending a alert that the application is
|
||||
// closing.
|
||||
func DeferExitHandler(handler func()) {
|
||||
handlers = append([]func(){handler}, handlers...)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
version: "{build}"
|
||||
platform: x64
|
||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
build_script:
|
||||
- go get -t
|
||||
- go test
|
||||
version: "{build}"
|
||||
platform: x64
|
||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
build_script:
|
||||
- go get -t
|
||||
- go test
|
||||
|
|
|
@ -2,13 +2,33 @@ package logrus
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var bufferPool *sync.Pool
|
||||
var (
|
||||
bufferPool *sync.Pool
|
||||
|
||||
// qualified package name, cached at first use
|
||||
logrusPackage string
|
||||
|
||||
// Positions in the call stack when tracing to report the calling method
|
||||
minimumCallerDepth int
|
||||
|
||||
// Used for caller information initialisation
|
||||
callerInitOnce sync.Once
|
||||
)
|
||||
|
||||
const (
|
||||
maximumCallerDepth int = 25
|
||||
knownLogrusFrames int = 4
|
||||
)
|
||||
|
||||
func init() {
|
||||
bufferPool = &sync.Pool{
|
||||
|
@ -16,15 +36,18 @@ func init() {
|
|||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
// start at the bottom of the stack before the package-name cache is primed
|
||||
minimumCallerDepth = 1
|
||||
}
|
||||
|
||||
// Defines the key when adding errors using WithError.
|
||||
var ErrorKey = "error"
|
||||
|
||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||
// passed around as much as you wish to avoid field duplication.
|
||||
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
||||
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
||||
// reused and passed around as much as you wish to avoid field duplication.
|
||||
type Entry struct {
|
||||
Logger *Logger
|
||||
|
||||
|
@ -34,22 +57,31 @@ type Entry struct {
|
|||
// Time at which the log entry was created
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||
Level Level
|
||||
|
||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||
// Calling method, with package name
|
||||
Caller *runtime.Frame
|
||||
|
||||
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||
Message string
|
||||
|
||||
// When formatter is called in entry.log(), an Buffer may be set to entry
|
||||
// When formatter is called in entry.log(), a Buffer may be set to entry
|
||||
Buffer *bytes.Buffer
|
||||
|
||||
// Contains the context set by the user. Useful for hook processing etc.
|
||||
Context context.Context
|
||||
|
||||
// err may contain a field formatting error
|
||||
err string
|
||||
}
|
||||
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is five fields, give a little extra room
|
||||
Data: make(Fields, 5),
|
||||
// Default is three fields, plus one optional. Give a little extra room.
|
||||
Data: make(Fields, 6),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,6 +101,11 @@ func (entry *Entry) WithError(err error) *Entry {
|
|||
return entry.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// Add a context to the Entry.
|
||||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||||
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: entry.Time, err: entry.err, Context: ctx}
|
||||
}
|
||||
|
||||
// Add a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
|
@ -80,15 +117,88 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
|
|||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
fieldErr := entry.err
|
||||
for k, v := range fields {
|
||||
data[k] = v
|
||||
isErrField := false
|
||||
if t := reflect.TypeOf(v); t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Func:
|
||||
isErrField = true
|
||||
case reflect.Ptr:
|
||||
isErrField = t.Elem().Kind() == reflect.Func
|
||||
}
|
||||
}
|
||||
if isErrField {
|
||||
tmp := fmt.Sprintf("can not add field %q", k)
|
||||
if fieldErr != "" {
|
||||
fieldErr = entry.err + ", " + tmp
|
||||
} else {
|
||||
fieldErr = tmp
|
||||
}
|
||||
} else {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time}
|
||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
||||
}
|
||||
|
||||
// Overrides the time of the Entry.
|
||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t}
|
||||
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err, Context: entry.Context}
|
||||
}
|
||||
|
||||
// getPackageName reduces a fully qualified function name to the package name
|
||||
// There really ought to be to be a better way...
|
||||
func getPackageName(f string) string {
|
||||
for {
|
||||
lastPeriod := strings.LastIndex(f, ".")
|
||||
lastSlash := strings.LastIndex(f, "/")
|
||||
if lastPeriod > lastSlash {
|
||||
f = f[:lastPeriod]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// getCaller retrieves the name of the first non-logrus calling function
|
||||
func getCaller() *runtime.Frame {
|
||||
|
||||
// cache this package's fully-qualified name
|
||||
callerInitOnce.Do(func() {
|
||||
pcs := make([]uintptr, 2)
|
||||
_ = runtime.Callers(0, pcs)
|
||||
logrusPackage = getPackageName(runtime.FuncForPC(pcs[1]).Name())
|
||||
|
||||
// now that we have the cache, we can skip a minimum count of known-logrus functions
|
||||
// XXX this is dubious, the number of frames may vary
|
||||
minimumCallerDepth = knownLogrusFrames
|
||||
})
|
||||
|
||||
// Restrict the lookback frames to avoid runaway lookups
|
||||
pcs := make([]uintptr, maximumCallerDepth)
|
||||
depth := runtime.Callers(minimumCallerDepth, pcs)
|
||||
frames := runtime.CallersFrames(pcs[:depth])
|
||||
|
||||
for f, again := frames.Next(); again; f, again = frames.Next() {
|
||||
pkg := getPackageName(f.Function)
|
||||
|
||||
// If the caller isn't part of this package, we're done
|
||||
if pkg != logrusPackage {
|
||||
return &f
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, we failed to find the caller's context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (entry Entry) HasCaller() (has bool) {
|
||||
return entry.Logger != nil &&
|
||||
entry.Logger.ReportCaller &&
|
||||
entry.Caller != nil
|
||||
}
|
||||
|
||||
// This function is not declared with a pointer value because otherwise
|
||||
|
@ -107,6 +217,9 @@ func (entry Entry) log(level Level, msg string) {
|
|||
|
||||
entry.Level = level
|
||||
entry.Message = msg
|
||||
if entry.Logger.ReportCaller {
|
||||
entry.Caller = getCaller()
|
||||
}
|
||||
|
||||
entry.fireHooks()
|
||||
|
||||
|
@ -150,26 +263,30 @@ func (entry *Entry) write() {
|
|||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(DebugLevel) {
|
||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||
func (entry *Entry) Log(level Level, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.log(level, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Trace(args ...interface{}) {
|
||||
entry.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
entry.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(InfoLevel) {
|
||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||
}
|
||||
entry.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(WarnLevel) {
|
||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||
}
|
||||
entry.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warning(args ...interface{}) {
|
||||
|
@ -177,37 +294,37 @@ func (entry *Entry) Warning(args ...interface{}) {
|
|||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(ErrorLevel) {
|
||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||
}
|
||||
entry.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(FatalLevel) {
|
||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||
}
|
||||
Exit(1)
|
||||
entry.Log(FatalLevel, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||
}
|
||||
entry.Log(PanicLevel, args...)
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(DebugLevel) {
|
||||
entry.Debug(fmt.Sprintf(format, args...))
|
||||
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.Log(level, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
||||
entry.Logf(TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
entry.Logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(InfoLevel) {
|
||||
entry.Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
entry.Logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
|
@ -215,9 +332,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) {
|
|||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(WarnLevel) {
|
||||
entry.Warn(fmt.Sprintf(format, args...))
|
||||
}
|
||||
entry.Logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
|
@ -225,36 +340,36 @@ func (entry *Entry) Warningf(format string, args ...interface{}) {
|
|||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(ErrorLevel) {
|
||||
entry.Error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
entry.Logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(FatalLevel) {
|
||||
entry.Fatal(fmt.Sprintf(format, args...))
|
||||
}
|
||||
Exit(1)
|
||||
entry.Logf(FatalLevel, format, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||
entry.Panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
entry.Logf(PanicLevel, format, args...)
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(DebugLevel) {
|
||||
entry.Debug(entry.sprintlnn(args...))
|
||||
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.Log(level, entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Traceln(args ...interface{}) {
|
||||
entry.Logln(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
entry.Logln(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(InfoLevel) {
|
||||
entry.Info(entry.sprintlnn(args...))
|
||||
}
|
||||
entry.Logln(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
|
@ -262,9 +377,7 @@ func (entry *Entry) Println(args ...interface{}) {
|
|||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(WarnLevel) {
|
||||
entry.Warn(entry.sprintlnn(args...))
|
||||
}
|
||||
entry.Logln(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
|
@ -272,22 +385,16 @@ func (entry *Entry) Warningln(args ...interface{}) {
|
|||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(ErrorLevel) {
|
||||
entry.Error(entry.sprintlnn(args...))
|
||||
}
|
||||
entry.Logln(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(FatalLevel) {
|
||||
entry.Fatal(entry.sprintlnn(args...))
|
||||
}
|
||||
Exit(1)
|
||||
entry.Logln(FatalLevel, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||
entry.Panic(entry.sprintlnn(args...))
|
||||
}
|
||||
entry.Logln(PanicLevel, args...)
|
||||
}
|
||||
|
||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
@ -24,6 +25,12 @@ func SetFormatter(formatter Formatter) {
|
|||
std.SetFormatter(formatter)
|
||||
}
|
||||
|
||||
// SetReportCaller sets whether the standard logger will include the calling
|
||||
// method as a field.
|
||||
func SetReportCaller(include bool) {
|
||||
std.SetReportCaller(include)
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level Level) {
|
||||
std.SetLevel(level)
|
||||
|
@ -49,6 +56,11 @@ func WithError(err error) *Entry {
|
|||
return std.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// WithContext creates an entry from the standard logger and adds a context to it.
|
||||
func WithContext(ctx context.Context) *Entry {
|
||||
return std.WithContext(ctx)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
|
@ -77,6 +89,11 @@ func WithTime(t time.Time) *Entry {
|
|||
return std.WithTime(t)
|
||||
}
|
||||
|
||||
// Trace logs a message at level Trace on the standard logger.
|
||||
func Trace(args ...interface{}) {
|
||||
std.Trace(args...)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
std.Debug(args...)
|
||||
|
@ -117,6 +134,11 @@ func Fatal(args ...interface{}) {
|
|||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// Tracef logs a message at level Trace on the standard logger.
|
||||
func Tracef(format string, args ...interface{}) {
|
||||
std.Tracef(format, args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
std.Debugf(format, args...)
|
||||
|
@ -157,6 +179,11 @@ func Fatalf(format string, args ...interface{}) {
|
|||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Traceln logs a message at level Trace on the standard logger.
|
||||
func Traceln(args ...interface{}) {
|
||||
std.Traceln(args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
std.Debugln(args...)
|
||||
|
|
|
@ -2,7 +2,16 @@ package logrus
|
|||
|
||||
import "time"
|
||||
|
||||
const defaultTimestampFormat = time.RFC3339
|
||||
// Default key names for the default fields
|
||||
const (
|
||||
defaultTimestampFormat = time.RFC3339
|
||||
FieldKeyMsg = "msg"
|
||||
FieldKeyLevel = "level"
|
||||
FieldKeyTime = "time"
|
||||
FieldKeyLogrusError = "logrus_error"
|
||||
FieldKeyFunc = "func"
|
||||
FieldKeyFile = "file"
|
||||
)
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
|
@ -18,7 +27,7 @@ type Formatter interface {
|
|||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
|
@ -30,7 +39,7 @@ type Formatter interface {
|
|||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields, fieldMap FieldMap) {
|
||||
func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) {
|
||||
timeKey := fieldMap.resolve(FieldKeyTime)
|
||||
if t, ok := data[timeKey]; ok {
|
||||
data["fields."+timeKey] = t
|
||||
|
@ -48,4 +57,22 @@ func prefixFieldClashes(data Fields, fieldMap FieldMap) {
|
|||
data["fields."+levelKey] = l
|
||||
delete(data, levelKey)
|
||||
}
|
||||
|
||||
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
|
||||
if l, ok := data[logrusErrKey]; ok {
|
||||
data["fields."+logrusErrKey] = l
|
||||
delete(data, logrusErrKey)
|
||||
}
|
||||
|
||||
// If reportCaller is not set, 'func' will not conflict.
|
||||
if reportCaller {
|
||||
funcKey := fieldMap.resolve(FieldKeyFunc)
|
||||
if l, ok := data[funcKey]; ok {
|
||||
data["fields."+funcKey] = l
|
||||
}
|
||||
fileKey := fieldMap.resolve(FieldKeyFile)
|
||||
if l, ok := data[fileKey]; ok {
|
||||
data["fields."+fileKey] = l
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
module github.com/sirupsen/logrus
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.1.1 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type fieldKey string
|
||||
|
@ -11,13 +12,6 @@ type fieldKey string
|
|||
// FieldMap allows customization of the key names for default fields.
|
||||
type FieldMap map[fieldKey]string
|
||||
|
||||
// Default key names for the default fields
|
||||
const (
|
||||
FieldKeyMsg = "msg"
|
||||
FieldKeyLevel = "level"
|
||||
FieldKeyTime = "time"
|
||||
)
|
||||
|
||||
func (f FieldMap) resolve(key fieldKey) string {
|
||||
if k, ok := f[key]; ok {
|
||||
return k
|
||||
|
@ -41,17 +35,27 @@ type JSONFormatter struct {
|
|||
// As an example:
|
||||
// formatter := &JSONFormatter{
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message",
|
||||
// FieldKeyMsg: "@message",
|
||||
// FieldKeyFunc: "@caller",
|
||||
// },
|
||||
// }
|
||||
FieldMap FieldMap
|
||||
|
||||
// CallerPrettyfier can be set by the user to modify the content
|
||||
// of the function and file keys in the json data when ReportCaller is
|
||||
// activated. If any of the returned value is the empty string the
|
||||
// corresponding key will be removed from json fields.
|
||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||
|
||||
// PrettyPrint will indent all json logs
|
||||
PrettyPrint bool
|
||||
}
|
||||
|
||||
// Format renders a single log entry
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+3)
|
||||
data := make(Fields, len(entry.Data)+4)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
|
@ -69,18 +73,34 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|||
data = newData
|
||||
}
|
||||
|
||||
prefixFieldClashes(data, f.FieldMap)
|
||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
|
||||
if entry.err != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
|
||||
}
|
||||
if !f.DisableTimestamp {
|
||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
||||
}
|
||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||
if entry.HasCaller() {
|
||||
funcVal := entry.Caller.Function
|
||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
}
|
||||
if funcVal != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
||||
}
|
||||
if fileVal != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
|
||||
}
|
||||
}
|
||||
|
||||
var b *bytes.Buffer
|
||||
if entry.Buffer != nil {
|
||||
|
@ -88,9 +108,14 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|||
} else {
|
||||
b = &bytes.Buffer{}
|
||||
}
|
||||
err := json.NewEncoder(b).Encode(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
|
||||
encoder := json.NewEncoder(b)
|
||||
if f.PrettyPrint {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
@ -24,6 +25,10 @@ type Logger struct {
|
|||
// own that implements the `Formatter` interface, see the `README` or included
|
||||
// formatters for examples.
|
||||
Formatter Formatter
|
||||
|
||||
// Flag for whether to log caller info (off by default)
|
||||
ReportCaller bool
|
||||
|
||||
// The logging level the logger should log at. This is typically (and defaults
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged.
|
||||
|
@ -32,8 +37,12 @@ type Logger struct {
|
|||
mu MutexWrap
|
||||
// Reusable empty entry
|
||||
entryPool sync.Pool
|
||||
// Function to exit the application, defaults to `os.Exit()`
|
||||
ExitFunc exitFunc
|
||||
}
|
||||
|
||||
type exitFunc func(int)
|
||||
|
||||
type MutexWrap struct {
|
||||
lock sync.Mutex
|
||||
disabled bool
|
||||
|
@ -69,10 +78,12 @@ func (mw *MutexWrap) Disable() {
|
|||
// It's recommended to make this a global instance called `log`.
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(LevelHooks),
|
||||
Level: InfoLevel,
|
||||
Out: os.Stderr,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(LevelHooks),
|
||||
Level: InfoLevel,
|
||||
ExitFunc: os.Exit,
|
||||
ReportCaller: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,6 +125,13 @@ func (logger *Logger) WithError(err error) *Entry {
|
|||
return entry.WithError(err)
|
||||
}
|
||||
|
||||
// Add a context to the log entry.
|
||||
func (logger *Logger) WithContext(ctx context.Context) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithContext(ctx)
|
||||
}
|
||||
|
||||
// Overrides the time of the log entry.
|
||||
func (logger *Logger) WithTime(t time.Time) *Entry {
|
||||
entry := logger.newEntry()
|
||||
|
@ -121,20 +139,24 @@ func (logger *Logger) WithTime(t time.Time) *Entry {
|
|||
return entry.WithTime(t)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(DebugLevel) {
|
||||
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Debugf(format, args...)
|
||||
entry.Logf(level, format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
||||
logger.Logf(TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
logger.Logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(InfoLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Infof(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
|
@ -144,123 +166,91 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
|
|||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(WarnLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(WarnLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(ErrorLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Errorf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(FatalLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatalf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
logger.Logf(FatalLevel, format, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(PanicLevel) {
|
||||
logger.Logf(PanicLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Log(level Level, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Panicf(format, args...)
|
||||
entry.Log(level, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Trace(args ...interface{}) {
|
||||
logger.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(DebugLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Debug(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(InfoLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Info(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Info(args...)
|
||||
entry.Print(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(WarnLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Warn(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(WarnLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Warn(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Warn(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(ErrorLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Error(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(FatalLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatal(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
logger.Log(FatalLevel, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(PanicLevel) {
|
||||
logger.Log(PanicLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Panic(args...)
|
||||
entry.Logln(level, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Traceln(args ...interface{}) {
|
||||
logger.Logln(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(DebugLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Debugln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Logln(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(InfoLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Infoln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Logln(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
|
@ -270,44 +260,32 @@ func (logger *Logger) Println(args ...interface{}) {
|
|||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(WarnLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Logln(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(WarnLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Warnln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(ErrorLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Errorln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
logger.Logln(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(FatalLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatalln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
logger.Logln(FatalLevel, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
if logger.IsLevelEnabled(PanicLevel) {
|
||||
entry := logger.newEntry()
|
||||
entry.Panicln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
logger.Logln(PanicLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Exit(code int) {
|
||||
runHandlers()
|
||||
if logger.ExitFunc == nil {
|
||||
logger.ExitFunc = os.Exit
|
||||
}
|
||||
logger.ExitFunc(code)
|
||||
}
|
||||
|
||||
//When file is opened with appending mode, it's safe to
|
||||
|
@ -357,6 +335,12 @@ func (logger *Logger) SetOutput(output io.Writer) {
|
|||
logger.Out = output
|
||||
}
|
||||
|
||||
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.ReportCaller = reportCaller
|
||||
}
|
||||
|
||||
// ReplaceHooks replaces the logger hooks and returns the old ones
|
||||
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
||||
logger.mu.Lock()
|
||||
|
|
|
@ -14,22 +14,11 @@ type Level uint32
|
|||
|
||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||
func (level Level) String() string {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case PanicLevel:
|
||||
return "panic"
|
||||
if b, err := level.MarshalText(); err == nil {
|
||||
return string(b)
|
||||
} else {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
|
@ -47,12 +36,47 @@ func ParseLevel(lvl string) (Level, error) {
|
|||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
case "trace":
|
||||
return TraceLevel, nil
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (level *Level) UnmarshalText(text []byte) error {
|
||||
l, err := ParseLevel(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*level = Level(l)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (level Level) MarshalText() ([]byte, error) {
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
return []byte("trace"), nil
|
||||
case DebugLevel:
|
||||
return []byte("debug"), nil
|
||||
case InfoLevel:
|
||||
return []byte("info"), nil
|
||||
case WarnLevel:
|
||||
return []byte("warning"), nil
|
||||
case ErrorLevel:
|
||||
return []byte("error"), nil
|
||||
case FatalLevel:
|
||||
return []byte("fatal"), nil
|
||||
case PanicLevel:
|
||||
return []byte("panic"), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
||||
}
|
||||
|
||||
// A constant exposing all logging levels
|
||||
var AllLevels = []Level{
|
||||
PanicLevel,
|
||||
|
@ -61,6 +85,7 @@ var AllLevels = []Level{
|
|||
WarnLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
TraceLevel,
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
|
@ -69,7 +94,7 @@ const (
|
|||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
|
@ -82,6 +107,8 @@ const (
|
|||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
||||
TraceLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
|
@ -148,3 +175,12 @@ type FieldLogger interface {
|
|||
// IsFatalEnabled() bool
|
||||
// IsPanicEnabled() bool
|
||||
}
|
||||
|
||||
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
|
||||
// here for consistancy. Do not use. Use Logger or Entry instead.
|
||||
type Ext1FieldLogger interface {
|
||||
FieldLogger
|
||||
Tracef(format string, args ...interface{})
|
||||
Trace(args ...interface{})
|
||||
Traceln(args ...interface{})
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
// +build !appengine,!js
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TIOCGETA
|
||||
|
||||
type Termios unix.Termios
|
|
@ -0,0 +1,13 @@
|
|||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TIOCGETA
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
return err == nil
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// +build js
|
||||
// +build js nacl plan9
|
||||
|
||||
package logrus
|
||||
|
|
@ -1,18 +1,16 @@
|
|||
// +build !appengine,!js
|
||||
// +build !appengine,!js,!windows,!nacl,!plan9
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
switch v := w.(type) {
|
||||
case *os.File:
|
||||
return terminal.IsTerminal(int(v.Fd()))
|
||||
return isTerminal(int(v.Fd()))
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
||||
return err == nil
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// +build linux aix
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
return err == nil
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// +build !appengine,!js,windows
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
sequences "github.com/konsorten/go-windows-terminal-sequences"
|
||||
)
|
||||
|
||||
func initTerminal(w io.Writer) {
|
||||
switch v := w.(type) {
|
||||
case *os.File:
|
||||
sequences.EnableVirtualTerminalProcessing(syscall.Handle(v.Fd()), true)
|
||||
}
|
||||
}
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
var ret bool
|
||||
switch v := w.(type) {
|
||||
case *os.File:
|
||||
var mode uint32
|
||||
err := syscall.GetConsoleMode(syscall.Handle(v.Fd()), &mode)
|
||||
ret = (err == nil)
|
||||
default:
|
||||
ret = false
|
||||
}
|
||||
if ret {
|
||||
initTerminal(w)
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// Based on ssh/terminal:
|
||||
// Copyright 2013 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.
|
||||
|
||||
// +build !appengine,!js
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
|
||||
type Termios unix.Termios
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -11,18 +12,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
nocolor = 0
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 36
|
||||
gray = 37
|
||||
red = 31
|
||||
yellow = 33
|
||||
blue = 36
|
||||
gray = 37
|
||||
)
|
||||
|
||||
var (
|
||||
baseTimestamp time.Time
|
||||
emptyFieldMap FieldMap
|
||||
)
|
||||
var baseTimestamp time.Time
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
|
@ -55,6 +51,9 @@ type TextFormatter struct {
|
|||
// be desired.
|
||||
DisableSorting bool
|
||||
|
||||
// The keys sorting function, when uninitialized it uses sort.Strings.
|
||||
SortingFunc func([]string)
|
||||
|
||||
// Disables the truncation of the level text to 4 characters.
|
||||
DisableLevelTruncation bool
|
||||
|
||||
|
@ -73,7 +72,13 @@ type TextFormatter struct {
|
|||
// FieldKeyMsg: "@message"}}
|
||||
FieldMap FieldMap
|
||||
|
||||
sync.Once
|
||||
// CallerPrettyfier can be set by the user to modify the content
|
||||
// of the function and file keys in the data when ReportCaller is
|
||||
// activated. If any of the returned value is the empty string the
|
||||
// corresponding key will be removed from fields.
|
||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||
|
||||
terminalInitOnce sync.Once
|
||||
}
|
||||
|
||||
func (f *TextFormatter) init(entry *Entry) {
|
||||
|
@ -83,7 +88,7 @@ func (f *TextFormatter) init(entry *Entry) {
|
|||
}
|
||||
|
||||
func (f *TextFormatter) isColored() bool {
|
||||
isColored := f.ForceColors || f.isTerminal
|
||||
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
||||
|
||||
if f.EnvironmentOverrideColors {
|
||||
if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
|
||||
|
@ -100,15 +105,59 @@ func (f *TextFormatter) isColored() bool {
|
|||
|
||||
// Format renders a single log entry
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
prefixFieldClashes(entry.Data, f.FieldMap)
|
||||
|
||||
keys := make([]string, 0, len(entry.Data))
|
||||
for k := range entry.Data {
|
||||
data := make(Fields)
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||
keys := make([]string, 0, len(data))
|
||||
for k := range data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
var funcVal, fileVal string
|
||||
|
||||
fixedKeys := make([]string, 0, 4+len(data))
|
||||
if !f.DisableTimestamp {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
|
||||
}
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
|
||||
if entry.Message != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
|
||||
}
|
||||
if entry.err != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
|
||||
}
|
||||
if entry.HasCaller() {
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
} else {
|
||||
funcVal = entry.Caller.Function
|
||||
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
}
|
||||
|
||||
if funcVal != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
|
||||
}
|
||||
if fileVal != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
|
||||
}
|
||||
}
|
||||
|
||||
if !f.DisableSorting {
|
||||
sort.Strings(keys)
|
||||
if f.SortingFunc == nil {
|
||||
sort.Strings(keys)
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
} else {
|
||||
if !f.isColored() {
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
f.SortingFunc(fixedKeys)
|
||||
} else {
|
||||
f.SortingFunc(keys)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
}
|
||||
|
||||
var b *bytes.Buffer
|
||||
|
@ -118,24 +167,35 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|||
b = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
f.Do(func() { f.init(entry) })
|
||||
f.terminalInitOnce.Do(func() { f.init(entry) })
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
if f.isColored() {
|
||||
f.printColored(b, entry, keys, timestampFormat)
|
||||
f.printColored(b, entry, keys, data, timestampFormat)
|
||||
} else {
|
||||
if !f.DisableTimestamp {
|
||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyTime), entry.Time.Format(timestampFormat))
|
||||
}
|
||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyLevel), entry.Level.String())
|
||||
if entry.Message != "" {
|
||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyMsg), entry.Message)
|
||||
}
|
||||
for _, key := range keys {
|
||||
f.appendKeyValue(b, key, entry.Data[key])
|
||||
|
||||
for _, key := range fixedKeys {
|
||||
var value interface{}
|
||||
switch {
|
||||
case key == f.FieldMap.resolve(FieldKeyTime):
|
||||
value = entry.Time.Format(timestampFormat)
|
||||
case key == f.FieldMap.resolve(FieldKeyLevel):
|
||||
value = entry.Level.String()
|
||||
case key == f.FieldMap.resolve(FieldKeyMsg):
|
||||
value = entry.Message
|
||||
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
||||
value = entry.err
|
||||
case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
|
||||
value = funcVal
|
||||
case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
|
||||
value = fileVal
|
||||
default:
|
||||
value = data[key]
|
||||
}
|
||||
f.appendKeyValue(b, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,10 +203,10 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
|
||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case DebugLevel:
|
||||
case DebugLevel, TraceLevel:
|
||||
levelColor = gray
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
|
@ -165,15 +225,33 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
|
|||
// the behavior of logrus text_formatter the same as the stdlib log package
|
||||
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
||||
|
||||
caller := ""
|
||||
if entry.HasCaller() {
|
||||
funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
|
||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
}
|
||||
|
||||
if fileVal == "" {
|
||||
caller = funcVal
|
||||
} else if funcVal == "" {
|
||||
caller = fileVal
|
||||
} else {
|
||||
caller = fileVal + " " + funcVal
|
||||
}
|
||||
}
|
||||
|
||||
if f.DisableTimestamp {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
||||
} else if !f.FullTimestamp {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
||||
} else {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
|
||||
}
|
||||
for _, k := range keys {
|
||||
v := entry.Data[k]
|
||||
v := data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
||||
f.appendValue(b, v)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
|||
var printFunc func(args ...interface{})
|
||||
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
printFunc = entry.Trace
|
||||
case DebugLevel:
|
||||
printFunc = entry.Debug
|
||||
case InfoLevel:
|
||||
|
|
|
@ -217,12 +217,12 @@
|
|||
{"path":"github.com/hashicorp/hcl/json/parser","checksumSHA1":"138aCV5n8n7tkGYMsMVQQnnLq+0=","revision":"6e968a3fcdcbab092f5307fd0d85479d5af1e4dc","revisionTime":"2016-11-01T18:00:25Z"},
|
||||
{"path":"github.com/hashicorp/hcl/json/scanner","checksumSHA1":"YdvFsNOMSWMLnY6fcliWQa0O5Fw=","revision":"6e968a3fcdcbab092f5307fd0d85479d5af1e4dc","revisionTime":"2016-11-01T18:00:25Z"},
|
||||
{"path":"github.com/hashicorp/hcl/json/token","checksumSHA1":"fNlXQCQEnb+B3k5UDL/r15xtSJY=","revision":"6e968a3fcdcbab092f5307fd0d85479d5af1e4dc","revisionTime":"2016-11-01T18:00:25Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/gohcl","checksumSHA1":"RFEjfMQWPAVILXE2PhL6wDW8Zg4=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl","checksumSHA1":"bUO4KS1yjAWa6miewgbUUsxYVfo=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl/hclsyntax","checksumSHA1":"uMWQk/2xJyIqL6ILq83VYVxkuY8=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl/json","checksumSHA1":"Vagtn0ywFboMp7br/xvzeuNUFNc=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcldec","checksumSHA1":"6JRj4T/iQxIe/CoKXHDjPuupmL8=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hclwrite","checksumSHA1":"DQLzlvDUtHL1DkYDZrMx2vfKJUg=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/gohcl","checksumSHA1":"RFEjfMQWPAVILXE2PhL6wDW8Zg4=","revision":"4fba5e1a75e382aed7f7a7993f2c4836a5e1cd52","revisionTime":"2019-06-17T16:00:22Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl","checksumSHA1":"1jDEGh+P7Cu8Lz39VudY6rRS6Jw=","revision":"4fba5e1a75e382aed7f7a7993f2c4836a5e1cd52","revisionTime":"2019-06-17T16:00:22Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl/hclsyntax","checksumSHA1":"6FZRBZj+je9sMM5wrhM5phUXxZU=","revision":"4fba5e1a75e382aed7f7a7993f2c4836a5e1cd52","revisionTime":"2019-06-17T16:00:22Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl/json","checksumSHA1":"l5KBeDDM1gf7yFth7WGhckpF+oc=","revision":"4fba5e1a75e382aed7f7a7993f2c4836a5e1cd52","revisionTime":"2019-06-17T16:00:22Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcldec","checksumSHA1":"6JRj4T/iQxIe/CoKXHDjPuupmL8=","revision":"4fba5e1a75e382aed7f7a7993f2c4836a5e1cd52","revisionTime":"2019-06-17T16:00:22Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/hcl2/hclwrite","checksumSHA1":"Hgs10IsC8LdotUKLavgOL35Mbw4=","revision":"4fba5e1a75e382aed7f7a7993f2c4836a5e1cd52","revisionTime":"2019-06-17T16:00:22Z","version":"master","versionExact":"master"},
|
||||
{"path":"github.com/hashicorp/logutils","checksumSHA1":"vt+P9D2yWDO3gdvdgCzwqunlhxU=","revision":"0dc08b1671f34c4250ce212759ebd880f743d883"},
|
||||
{"path":"github.com/hashicorp/memberlist","checksumSHA1":"yAu2gPVXIh28yJ2If5gZPrf04kU=","revision":"1a62499c21db33d57691001d5e08a71ec857b18f","revisionTime":"2019-01-03T22:22:36Z"},
|
||||
{"path":"github.com/hashicorp/net-rpc-msgpackrpc","checksumSHA1":"qnlqWJYV81ENr61SZk9c65R1mDo=","revision":"a14192a58a694c123d8fe5481d4a4727d6ae82f3"},
|
||||
|
@ -242,6 +242,7 @@
|
|||
{"path":"github.com/hpcloud/tail/util","checksumSHA1":"0xM336Lb25URO/1W1/CtGoRygVU=","revision":"37f4271387456dd1bf82ab1ad9229f060cc45386","revisionTime":"2017-08-14T16:06:53Z"},
|
||||
{"path":"github.com/hpcloud/tail/watch","checksumSHA1":"TP4OAv5JMtzj2TB6OQBKqauaKDc=","revision":"37f4271387456dd1bf82ab1ad9229f060cc45386","revisionTime":"2017-08-14T16:06:53Z"},
|
||||
{"path":"github.com/jmespath/go-jmespath","checksumSHA1":"3/Bhy+ua/DCv2ElMD5GzOYSGN6g=","comment":"0.2.2-2-gc01cf91","revision":"c01cf91b011868172fdcd9f41838e80c9d716264"},
|
||||
{"path":"github.com/konsorten/go-windows-terminal-sequences","checksumSHA1":"Aulh7C5SVOA4Jzt5eHNH6197Mbk=","revision":"f55edac94c9bbba5d6182a4be46d86a2c9b5b50e","revisionTime":"2019-02-26T22:47:05Z"},
|
||||
{"path":"github.com/kr/pretty","checksumSHA1":"eOXF2PEvYLMeD8DSzLZJWbjYzco=","revision":"cfb55aafdaf3ec08f0db22699ab822c50091b1c4","revisionTime":"2016-08-23T17:07:15Z"},
|
||||
{"path":"github.com/kr/pty","checksumSHA1":"WD7GMln/NoduJr0DbumjOE59xI8=","revision":"b6e1bdd4a4f88614e0c6e5e8089c7abed98aae17","revisionTime":"2019-04-01T03:15:51Z"},
|
||||
{"path":"github.com/kr/text","checksumSHA1":"uulQHQ7IsRKqDudBC8Go9J0gtAc=","revision":"7cafcd837844e784b526369c9bce262804aebc60","revisionTime":"2016-05-04T02:26:26Z"},
|
||||
|
@ -303,27 +304,28 @@
|
|||
{"path":"github.com/opencontainers/go-digest","checksumSHA1":"NTperEHVh1uBqfTy9+oKceN4tKI=","revision":"21dfd564fd89c944783d00d069f33e3e7123c448","revisionTime":"2017-01-11T18:16:59Z"},
|
||||
{"path":"github.com/opencontainers/image-spec/specs-go","checksumSHA1":"ZGlIwSRjdLYCUII7JLE++N4w7Xc=","revision":"89b51c794e9113108a2914e38e66c826a649f2b5","revisionTime":"2017-11-03T11:36:04Z"},
|
||||
{"path":"github.com/opencontainers/image-spec/specs-go/v1","checksumSHA1":"jdbXRRzeu0njLE9/nCEZG+Yg/Jk=","revision":"89b51c794e9113108a2914e38e66c826a649f2b5","revisionTime":"2017-11-03T11:36:04Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer","checksumSHA1":"tS+X1UWBpwuDSk+lC8JelSKpzpQ=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/apparmor","checksumSHA1":"gVVY8k2G3ws+V1czsfxfuRs8log=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/cgroups","checksumSHA1":"Ku9h5AOZZyF7LIoruJ26Ut+1WRI=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/cgroups/fs","checksumSHA1":"OnnBJ2WfB/Y9EQpABKetBedf6ts=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/cgroups/systemd","checksumSHA1":"941jSDfCIl+b1pIQwZ9r+wj8wvM=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/configs","checksumSHA1":"v9sgw4eYRNSsJUSG33OoFIwLqRI=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/configs/validate","checksumSHA1":"hUveFGK1HhGenf0OVoYZWccoW9I=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer","checksumSHA1":"OJlgvnpJuV+SDPW48YVUKWDbOnU=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/apparmor","checksumSHA1":"gVVY8k2G3ws+V1czsfxfuRs8log=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/cgroups","checksumSHA1":"aWtm1zkVCz9l2/zQNfnc246yQew=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/cgroups/fs","checksumSHA1":"OnnBJ2WfB/Y9EQpABKetBedf6ts=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/cgroups/systemd","checksumSHA1":"d7B9MiKb1k1Egh5qkNokIfcZ+OY=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/configs","checksumSHA1":"v9sgw4eYRNSsJUSG33OoFIwLqRI=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/configs/validate","checksumSHA1":"hUveFGK1HhGenf0OVoYZWccoW9I=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/criurpc","checksumSHA1":"n7G7Egz/tOPacXuq+nkvnFai3eU=","revision":"369b920277d27630441336775cd728bc0f19e496","revisionTime":"2018-09-07T18:53:11Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/devices","checksumSHA1":"2CwtFvz9kB0RSjFlcCkmq4taJ9U=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/intelrdt","checksumSHA1":"sAbowQ7hjveSH5ADUD9IYXnEAJM=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/keys","checksumSHA1":"mKxBw0il2IWjWYgksX+17ufDw34=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/mount","checksumSHA1":"MJiogPDUU2nFr1fzQU6T+Ry1W8o=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/nsenter","checksumSHA1":"VKq3tOnDPQsnwc4g3qXLlqkVt4g=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/seccomp","checksumSHA1":"I1Qw/btE1twMqKHpYNsC98cteak=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/stacktrace","checksumSHA1":"yp/kYBgVqKtxlnpq4CmyxLFMAE4=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/system","checksumSHA1":"cjg/UcueM1/2/ExZ3N7010sa+hI=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/user","checksumSHA1":"mdUukOXCVJxmT0CufSKDeMg5JFM=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/utils","checksumSHA1":"PqGgeBjTHnyGrTr5ekLFEXpC3iQ=","revision":"f56b4cbeadc407e715d9b2ba49e62185bd81cef4","revisionTime":"2019-03-16T06:30:56Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/devices","checksumSHA1":"2CwtFvz9kB0RSjFlcCkmq4taJ9U=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/intelrdt","checksumSHA1":"sAbowQ7hjveSH5ADUD9IYXnEAJM=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/keys","checksumSHA1":"mKxBw0il2IWjWYgksX+17ufDw34=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/logs","checksumSHA1":"mBbwlspKSImoGTw4uKE40AX3PYs=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/mount","checksumSHA1":"MJiogPDUU2nFr1fzQU6T+Ry1W8o=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/nsenter","checksumSHA1":"PnGFQdbZhZ4pcxFtQep5MEQ4/8E=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/seccomp","checksumSHA1":"I1Qw/btE1twMqKHpYNsC98cteak=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/stacktrace","checksumSHA1":"yp/kYBgVqKtxlnpq4CmyxLFMAE4=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/system","checksumSHA1":"cjg/UcueM1/2/ExZ3N7010sa+hI=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/user","checksumSHA1":"mdUukOXCVJxmT0CufSKDeMg5JFM=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runc/libcontainer/utils","checksumSHA1":"PqGgeBjTHnyGrTr5ekLFEXpC3iQ=","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/runtime-spec/specs-go","checksumSHA1":"AMYc2X2O/IL6EGrq6lTl5vEhLiY=","origin":"github.com/opencontainers/runc/vendor/github.com/opencontainers/runtime-spec/specs-go","revision":"459bfaec1fc6c17d8bfb12d0a0f69e7e7271ed2a","revisionTime":"2018-08-23T14:46:37Z"},
|
||||
{"path":"github.com/opencontainers/selinux/go-selinux","checksumSHA1":"j9efF9bPmCCag+LzqwjyB8a44B8=","origin":"github.com/opencontainers/runc/vendor/github.com/opencontainers/selinux/go-selinux","revision":"459bfaec1fc6c17d8bfb12d0a0f69e7e7271ed2a","revisionTime":"2018-08-23T14:46:37Z"},
|
||||
{"path":"github.com/opencontainers/selinux/go-selinux/label","checksumSHA1":"QbeVoKIoaJWZDH8V/588i8/Pjjs=","origin":"github.com/opencontainers/runc/vendor/github.com/opencontainers/selinux/go-selinux/label","revision":"459bfaec1fc6c17d8bfb12d0a0f69e7e7271ed2a","revisionTime":"2018-08-23T14:46:37Z"},
|
||||
{"path":"github.com/opencontainers/selinux/go-selinux","checksumSHA1":"X4jufgAG12FCYi0U9VK4DK5vvXo=","origin":"github.com/opencontainers/runc/vendor/github.com/opencontainers/selinux/go-selinux","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/opencontainers/selinux/go-selinux/label","checksumSHA1":"5hHzDoXjeTtGaVzyN8lOowcwvdQ=","origin":"github.com/opencontainers/runc/vendor/github.com/opencontainers/selinux/go-selinux/label","revision":"6cc515888830787a93d82138821f0309ad970640","revisionTime":"2019-06-11T12:12:36Z"},
|
||||
{"path":"github.com/pkg/errors","checksumSHA1":"ynJSWoF6v+3zMnh9R0QmmG6iGV8=","revision":"248dadf4e9068a0b3e79f02ed0a610d935de5302","revisionTime":"2016-10-29T09:36:37Z"},
|
||||
{"path":"github.com/pmezard/go-difflib/difflib","checksumSHA1":"LuFv4/jlrmFNnDb/5SCSEPAM9vU=","revision":"792786c7400a136282c1664665ae0a8db921c6c2","revisionTime":"2016-01-10T10:55:54Z"},
|
||||
{"path":"github.com/posener/complete","checksumSHA1":"rTNABfFJ9wtLQRH8uYNkEZGQOrY=","revision":"9f41f7636a724791a3b8b1d35e84caa1124f0d3c","revisionTime":"2017-08-29T17:11:12Z"},
|
||||
|
@ -354,7 +356,7 @@
|
|||
{"path":"github.com/shirou/gopsutil/net","checksumSHA1":"ME2P9hiaHO/YdVrNInDmb/dB6us=","origin":"github.com/hashicorp/gopsutil/net","revision":"62d5761ddb7d04fc475d6316c79ff0eed1e5f96b","revisionTime":"2018-04-27T01:21:16Z","tree":true},
|
||||
{"path":"github.com/shirou/gopsutil/process","checksumSHA1":"JuBAKUuSyR7RdJELt6tQMn79Y6w=","origin":"github.com/hashicorp/gopsutil/process","revision":"62d5761ddb7d04fc475d6316c79ff0eed1e5f96b","revisionTime":"2018-04-27T01:21:16Z","tree":true},
|
||||
{"path":"github.com/shirou/w32","checksumSHA1":"Nve7SpDmjsv6+rhkXAkfg/UQx94=","revision":"bb4de0191aa41b5507caa14b0650cdbddcd9280b","revisionTime":"2016-09-30T03:27:40Z"},
|
||||
{"path":"github.com/sirupsen/logrus","checksumSHA1":"KXUHFmNxt6vTQkUJ00c+M/+dV58=","revision":"51df1d314861d341546c26e4354ce760fe4bb02b","revisionTime":"2018-08-27T05:22:11Z"},
|
||||
{"path":"github.com/sirupsen/logrus","checksumSHA1":"SVO+Zm6SNwWrpgyc6mcTXbH4aJ8=","revision":"2a22dbedbad1fd454910cd1f44f210ef90c28464","revisionTime":"2019-05-18T13:52:02Z"},
|
||||
{"path":"github.com/skratchdot/open-golang/open","checksumSHA1":"h/HMhokbQHTdLUbruoBBTee+NYw=","revision":"75fb7ed4208cf72d323d7d02fd1a5964a7a9073c","revisionTime":"2016-03-02T14:40:31Z"},
|
||||
{"path":"github.com/spf13/pflag","checksumSHA1":"Q52Y7t0lEtk/wcDn5q7tS7B+jqs=","revision":"7aff26db30c1be810f9de5038ec5ef96ac41fd7c","revisionTime":"2017-08-24T17:57:12Z"},
|
||||
{"path":"github.com/stretchr/objx","checksumSHA1":"K0crHygPTP42i1nLKWphSlvOQJw=","revision":"1a9d0bb9f541897e62256577b352fdbc1fb4fd94","revisionTime":"2015-09-28T12:21:52Z"},
|
||||
|
|
|
@ -129,7 +129,7 @@ This endpoint retrieves its latest Autopilot configuration.
|
|||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ---------------------------- | -------------------------- |
|
||||
| `GET` | `/operator/autopilot/configuration` | `application/json` |
|
||||
| `GET` | `/v1/operator/autopilot/configuration` | `application/json` |
|
||||
|
||||
The table below shows this endpoint's support for
|
||||
[blocking queries](/api/index.html#blocking-queries) and
|
||||
|
@ -143,7 +143,7 @@ The table below shows this endpoint's support for
|
|||
|
||||
```text
|
||||
$ curl \
|
||||
https://localhost:4646/operator/autopilot/configuration
|
||||
https://localhost:4646/v1/operator/autopilot/configuration
|
||||
```
|
||||
|
||||
### Sample Response
|
||||
|
@ -171,7 +171,7 @@ This endpoint updates the Autopilot configuration of the cluster.
|
|||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ---------------------------- | -------------------------- |
|
||||
| `PUT` | `/operator/autopilot/configuration` | `application/json` |
|
||||
| `PUT` | `/v1/operator/autopilot/configuration` | `application/json` |
|
||||
|
||||
The table below shows this endpoint's support for
|
||||
[blocking queries](/api/index.html#blocking-queries) and
|
||||
|
@ -235,7 +235,7 @@ This endpoint queries the health of the autopilot status.
|
|||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ---------------------------- | -------------------------- |
|
||||
| `GET` | `/operator/autopilot/health` | `application/json` |
|
||||
| `GET` | `/v1/operator/autopilot/health` | `application/json` |
|
||||
|
||||
The table below shows this endpoint's support for
|
||||
[blocking queries](/api/index.html#blocking-queries) and
|
||||
|
@ -335,7 +335,7 @@ the future.
|
|||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ---------------------------- | -------------------------- |
|
||||
| `GET` | `/operator/scheduler/configuration` | `application/json` |
|
||||
| `GET` | `/v1/operator/scheduler/configuration` | `application/json` |
|
||||
|
||||
The table below shows this endpoint's support for
|
||||
[blocking queries](/api/index.html#blocking-queries) and
|
||||
|
@ -349,7 +349,7 @@ The table below shows this endpoint's support for
|
|||
|
||||
```text
|
||||
$ curl \
|
||||
https://localhost:4646/operator/scheduler/configuration
|
||||
https://localhost:4646/v1/operator/scheduler/configuration
|
||||
```
|
||||
|
||||
### Sample Response
|
||||
|
@ -394,7 +394,7 @@ This endpoint updates the scheduler configuration of the cluster.
|
|||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ---------------------------- | -------------------------- |
|
||||
| `PUT`, `POST` | `/operator/scheduler/configuration` | `application/json` |
|
||||
| `PUT`, `POST` | `/v1/operator/scheduler/configuration` | `application/json` |
|
||||
|
||||
The table below shows this endpoint's support for
|
||||
[blocking queries](/api/index.html#blocking-queries) and
|
||||
|
|
Loading…
Reference in New Issue