mirror of
https://github.com/containers/podman.git
synced 2025-05-20 00:27:03 +08:00
journald event logging
add the ability for podman to read and write events to journald instead of just a logfile. This can be controlled in libpod.conf with the `events_logger` attribute of `journald` or `file`. The default will be set to `journald`. Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
@ -55,6 +55,7 @@ then
|
||||
# Some setup needs to vary between distros
|
||||
case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in
|
||||
ubuntu-18)
|
||||
sudo apt-get -qq -y install libsystemd-dev
|
||||
# Always install runc on Ubuntu
|
||||
install_runc_from_git
|
||||
;;
|
||||
|
@ -9,7 +9,9 @@ podman\-events - Monitor Podman events
|
||||
## DESCRIPTION
|
||||
|
||||
Monitor and print events that occur in Podman. Each event will include a timestamp,
|
||||
a type, a status, name (if applicable), and image (if applicable).
|
||||
a type, a status, name (if applicable), and image (if applicable). The default logging
|
||||
mechanism is *journald*. This can be changed in libpod.conf by changing the `events_logger`
|
||||
value to `file`. Only `file` and `journald` are the accepted.
|
||||
|
||||
The *container* event type will report the follow statuses:
|
||||
* attach
|
||||
|
@ -113,3 +113,7 @@ runc = [
|
||||
"/bin/runc",
|
||||
"/usr/lib/cri-o-runc/sbin/runc"
|
||||
]
|
||||
|
||||
# Selects which logging mechanism to use for Podman events. Valid values
|
||||
# are `journald` or `file`.
|
||||
events_logger = "journald"
|
||||
|
@ -1,14 +1,19 @@
|
||||
package libpod
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/containers/libpod/libpod/events"
|
||||
"github.com/hpcloud/tail"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// newEventer returns an eventer that can be used to read/write events
|
||||
func (r *Runtime) newEventer() (events.Eventer, error) {
|
||||
options := events.EventerOptions{
|
||||
EventerType: r.config.EventsLogger,
|
||||
LogFilePath: r.config.EventsLogFilePath,
|
||||
}
|
||||
return events.NewEventer(options)
|
||||
}
|
||||
|
||||
// newContainerEvent creates a new event based on a container
|
||||
func (c *Container) newContainerEvent(status events.Status) {
|
||||
e := events.NewEvent(status)
|
||||
@ -16,8 +21,8 @@ func (c *Container) newContainerEvent(status events.Status) {
|
||||
e.Name = c.Name()
|
||||
e.Image = c.config.RootfsImageName
|
||||
e.Type = events.Container
|
||||
if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil {
|
||||
logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath)
|
||||
if err := c.runtime.eventer.Write(e); err != nil {
|
||||
logrus.Errorf("unable to write pod event: %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,8 +34,8 @@ func (c *Container) newContainerExitedEvent(exitCode int32) {
|
||||
e.Image = c.config.RootfsImageName
|
||||
e.Type = events.Container
|
||||
e.ContainerExitCode = int(exitCode)
|
||||
if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil {
|
||||
logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath)
|
||||
if err := c.runtime.eventer.Write(e); err != nil {
|
||||
logrus.Errorf("unable to write pod event: %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,8 +45,8 @@ func (p *Pod) newPodEvent(status events.Status) {
|
||||
e.ID = p.ID()
|
||||
e.Name = p.Name()
|
||||
e.Type = events.Pod
|
||||
if err := e.Write(p.runtime.config.EventsLogFilePath); err != nil {
|
||||
logrus.Errorf("unable to write event to %s", p.runtime.config.EventsLogFilePath)
|
||||
if err := p.runtime.eventer.Write(e); err != nil {
|
||||
logrus.Errorf("unable to write pod event: %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,51 +55,17 @@ func (v *Volume) newVolumeEvent(status events.Status) {
|
||||
e := events.NewEvent(status)
|
||||
e.Name = v.Name()
|
||||
e.Type = events.Volume
|
||||
if err := e.Write(v.runtime.config.EventsLogFilePath); err != nil {
|
||||
logrus.Errorf("unable to write event to %s", v.runtime.config.EventsLogFilePath)
|
||||
if err := v.runtime.eventer.Write(e); err != nil {
|
||||
logrus.Errorf("unable to write volume event: %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Events is a wrapper function for everyone to begin tailing the events log
|
||||
// with options
|
||||
func (r *Runtime) Events(fromStart, stream bool, options []events.EventFilter, eventChannel chan *events.Event) error {
|
||||
if !r.valid {
|
||||
return ErrRuntimeStopped
|
||||
}
|
||||
|
||||
t, err := r.getTail(fromStart, stream)
|
||||
func (r *Runtime) Events(options events.ReadOptions) error {
|
||||
eventer, err := r.newEventer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for line := range t.Lines {
|
||||
event, err := events.NewEventFromString(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch event.Type {
|
||||
case events.Image, events.Volume, events.Pod, events.Container:
|
||||
// no-op
|
||||
default:
|
||||
return errors.Errorf("event type %s is not valid in %s", event.Type.String(), r.config.EventsLogFilePath)
|
||||
}
|
||||
include := true
|
||||
for _, filter := range options {
|
||||
include = include && filter(event)
|
||||
}
|
||||
if include {
|
||||
eventChannel <- event
|
||||
}
|
||||
}
|
||||
close(eventChannel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) {
|
||||
reopen := true
|
||||
seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END}
|
||||
if fromStart || !stream {
|
||||
seek.Whence = 0
|
||||
reopen = false
|
||||
}
|
||||
return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger})
|
||||
return eventer.Read(options)
|
||||
}
|
||||
|
149
libpod/events/config.go
Normal file
149
libpod/events/config.go
Normal file
@ -0,0 +1,149 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// EventerType ...
|
||||
type EventerType int
|
||||
|
||||
const (
|
||||
// LogFile indicates the event logger will be a logfile
|
||||
LogFile EventerType = iota
|
||||
// Journald indicates journald should be used to log events
|
||||
Journald EventerType = iota
|
||||
)
|
||||
|
||||
// Event describes the attributes of a libpod event
|
||||
type Event struct {
|
||||
// ContainerExitCode is for storing the exit code of a container which can
|
||||
// be used for "internal" event notification
|
||||
ContainerExitCode int
|
||||
// ID can be for the container, image, volume, etc
|
||||
ID string
|
||||
// Image used where applicable
|
||||
Image string
|
||||
// Name where applicable
|
||||
Name string
|
||||
// Status describes the event that occurred
|
||||
Status Status
|
||||
// Time the event occurred
|
||||
Time time.Time
|
||||
// Type of event that occurred
|
||||
Type Type
|
||||
}
|
||||
|
||||
// EventerOptions describe options that need to be passed to create
|
||||
// an eventer
|
||||
type EventerOptions struct {
|
||||
// EventerType describes whether to use journald or a file
|
||||
EventerType string
|
||||
// LogFilePath is the path to where the log file should reside if using
|
||||
// the file logger
|
||||
LogFilePath string
|
||||
}
|
||||
|
||||
// Eventer is the interface for journald or file event logging
|
||||
type Eventer interface {
|
||||
// Write an event to a backend
|
||||
Write(event Event) error
|
||||
// Read an event from the backend
|
||||
Read(options ReadOptions) error
|
||||
}
|
||||
|
||||
// ReadOptions describe the attributes needed to read event logs
|
||||
type ReadOptions struct {
|
||||
// EventChannel is the comm path back to user
|
||||
EventChannel chan *Event
|
||||
// Filters are key/value pairs that describe to limit output
|
||||
Filters []string
|
||||
// FromStart means you start reading from the start of the logs
|
||||
FromStart bool
|
||||
// Since reads "since" the given time
|
||||
Since string
|
||||
// Stream is follow
|
||||
Stream bool
|
||||
// Until reads "until" the given time
|
||||
Until string
|
||||
}
|
||||
|
||||
// Type of event that occurred (container, volume, image, pod, etc)
|
||||
type Type string
|
||||
|
||||
// Status describes the actual event action (stop, start, create, kill)
|
||||
type Status string
|
||||
|
||||
const (
|
||||
// If you add or subtract any values to the following lists, make sure you also update
|
||||
// the switch statements below and the enums for EventType or EventStatus in the
|
||||
// varlink description file.
|
||||
|
||||
// Container - event is related to containers
|
||||
Container Type = "container"
|
||||
// Image - event is related to images
|
||||
Image Type = "image"
|
||||
// Pod - event is related to pods
|
||||
Pod Type = "pod"
|
||||
// Volume - event is related to volumes
|
||||
Volume Type = "volume"
|
||||
|
||||
// Attach ...
|
||||
Attach Status = "attach"
|
||||
// Checkpoint ...
|
||||
Checkpoint Status = "checkpoint"
|
||||
// Cleanup ...
|
||||
Cleanup Status = "cleanup"
|
||||
// Commit ...
|
||||
Commit Status = "commit"
|
||||
// Create ...
|
||||
Create Status = "create"
|
||||
// Exec ...
|
||||
Exec Status = "exec"
|
||||
// Exited indicates that a container's process died
|
||||
Exited Status = "died"
|
||||
// Export ...
|
||||
Export Status = "export"
|
||||
// History ...
|
||||
History Status = "history"
|
||||
// Import ...
|
||||
Import Status = "import"
|
||||
// Init ...
|
||||
Init Status = "init"
|
||||
// Kill ...
|
||||
Kill Status = "kill"
|
||||
// LoadFromArchive ...
|
||||
LoadFromArchive Status = "loadfromarchive"
|
||||
// Mount ...
|
||||
Mount Status = "mount"
|
||||
// Pause ...
|
||||
Pause Status = "pause"
|
||||
// Prune ...
|
||||
Prune Status = "prune"
|
||||
// Pull ...
|
||||
Pull Status = "pull"
|
||||
// Push ...
|
||||
Push Status = "push"
|
||||
// Remove ...
|
||||
Remove Status = "remove"
|
||||
// Restore ...
|
||||
Restore Status = "restore"
|
||||
// Save ...
|
||||
Save Status = "save"
|
||||
// Start ...
|
||||
Start Status = "start"
|
||||
// Stop ...
|
||||
Stop Status = "stop"
|
||||
// Sync ...
|
||||
Sync Status = "sync"
|
||||
// Tag ...
|
||||
Tag Status = "tag"
|
||||
// Unmount ...
|
||||
Unmount Status = "unmount"
|
||||
// Unpause ...
|
||||
Unpause Status = "unpause"
|
||||
// Untag ...
|
||||
Untag Status = "untag"
|
||||
)
|
||||
|
||||
// EventFilter for filtering events
|
||||
type EventFilter func(*Event) bool
|
@ -6,110 +6,19 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage"
|
||||
"github.com/hpcloud/tail"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Event describes the attributes of a libpod event
|
||||
type Event struct {
|
||||
// ContainerExitCode is for storing the exit code of a container which can
|
||||
// be used for "internal" event notification
|
||||
ContainerExitCode int
|
||||
// ID can be for the container, image, volume, etc
|
||||
ID string
|
||||
// Image used where applicable
|
||||
Image string
|
||||
// Name where applicable
|
||||
Name string
|
||||
// Status describes the event that occurred
|
||||
Status Status
|
||||
// Time the event occurred
|
||||
Time time.Time
|
||||
// Type of event that occurred
|
||||
Type Type
|
||||
// String returns a string representation of EventerType
|
||||
func (et EventerType) String() string {
|
||||
if et == LogFile {
|
||||
return "file"
|
||||
|
||||
}
|
||||
return "journald"
|
||||
}
|
||||
|
||||
// Type of event that occurred (container, volume, image, pod, etc)
|
||||
type Type string
|
||||
|
||||
// Status describes the actual event action (stop, start, create, kill)
|
||||
type Status string
|
||||
|
||||
const (
|
||||
// If you add or subtract any values to the following lists, make sure you also update
|
||||
// the switch statements below and the enums for EventType or EventStatus in the
|
||||
// varlink description file.
|
||||
|
||||
// Container - event is related to containers
|
||||
Container Type = "container"
|
||||
// Image - event is related to images
|
||||
Image Type = "image"
|
||||
// Pod - event is related to pods
|
||||
Pod Type = "pod"
|
||||
// Volume - event is related to volumes
|
||||
Volume Type = "volume"
|
||||
|
||||
// Attach ...
|
||||
Attach Status = "attach"
|
||||
// Checkpoint ...
|
||||
Checkpoint Status = "checkpoint"
|
||||
// Cleanup ...
|
||||
Cleanup Status = "cleanup"
|
||||
// Commit ...
|
||||
Commit Status = "commit"
|
||||
// Create ...
|
||||
Create Status = "create"
|
||||
// Exec ...
|
||||
Exec Status = "exec"
|
||||
// Exited indicates that a container's process died
|
||||
Exited Status = "died"
|
||||
// Export ...
|
||||
Export Status = "export"
|
||||
// History ...
|
||||
History Status = "history"
|
||||
// Import ...
|
||||
Import Status = "import"
|
||||
// Init ...
|
||||
Init Status = "init"
|
||||
// Kill ...
|
||||
Kill Status = "kill"
|
||||
// LoadFromArchive ...
|
||||
LoadFromArchive Status = "status"
|
||||
// Mount ...
|
||||
Mount Status = "mount"
|
||||
// Pause ...
|
||||
Pause Status = "pause"
|
||||
// Prune ...
|
||||
Prune Status = "prune"
|
||||
// Pull ...
|
||||
Pull Status = "pull"
|
||||
// Push ...
|
||||
Push Status = "push"
|
||||
// Remove ...
|
||||
Remove Status = "remove"
|
||||
// Restore ...
|
||||
Restore Status = "restore"
|
||||
// Save ...
|
||||
Save Status = "save"
|
||||
// Start ...
|
||||
Start Status = "start"
|
||||
// Stop ...
|
||||
Stop Status = "stop"
|
||||
// Sync ...
|
||||
Sync Status = "sync"
|
||||
// Tag ...
|
||||
Tag Status = "tag"
|
||||
// Unmount ...
|
||||
Unmount Status = "unmount"
|
||||
// Unpause ...
|
||||
Unpause Status = "unpause"
|
||||
// Untag ...
|
||||
Untag Status = "untag"
|
||||
)
|
||||
|
||||
// EventFilter for filtering events
|
||||
type EventFilter func(*Event) bool
|
||||
|
||||
// NewEvent creates a event struct and populates with
|
||||
// the given status and time.
|
||||
func NewEvent(status Status) Event {
|
||||
@ -119,30 +28,6 @@ func NewEvent(status Status) Event {
|
||||
}
|
||||
}
|
||||
|
||||
// Write will record the event to the given path
|
||||
func (e *Event) Write(path string) error {
|
||||
// We need to lock events file
|
||||
lock, err := storage.GetLockfile(path + ".lock")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
eventJSONString, err := e.ToJSONString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recycle checks if the event log has reach a limit and if so
|
||||
// renames the current log and starts a new one. The remove bool
|
||||
// indicates the old log file should be deleted.
|
||||
@ -172,7 +57,7 @@ func (e *Event) ToHumanReadable() string {
|
||||
|
||||
// NewEventFromString takes stringified json and converts
|
||||
// it to an event
|
||||
func NewEventFromString(event string) (*Event, error) {
|
||||
func newEventFromJSONString(event string) (*Event, error) {
|
||||
e := Event{}
|
||||
if err := json.Unmarshal([]byte(event), &e); err != nil {
|
||||
return nil, err
|
||||
@ -222,6 +107,7 @@ func StringToStatus(name string) (Status, error) {
|
||||
case Commit.String():
|
||||
return Commit, nil
|
||||
case Create.String():
|
||||
|
||||
return Create, nil
|
||||
case Exec.String():
|
||||
return Exec, nil
|
||||
@ -270,3 +156,17 @@ func StringToStatus(name string) (Status, error) {
|
||||
}
|
||||
return "", errors.Errorf("unknown event status %s", name)
|
||||
}
|
||||
|
||||
func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) {
|
||||
reopen := true
|
||||
seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END}
|
||||
if options.FromStart || !options.Stream {
|
||||
seek.Whence = 0
|
||||
reopen = false
|
||||
}
|
||||
stream := options.Stream
|
||||
if len(options.Until) > 0 {
|
||||
stream = false
|
||||
}
|
||||
return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger})
|
||||
}
|
||||
|
20
libpod/events/events_linux.go
Normal file
20
libpod/events/events_linux.go
Normal file
@ -0,0 +1,20 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewEventer creates an eventer based on the eventer type
|
||||
func NewEventer(options EventerOptions) (Eventer, error) {
|
||||
var eventer Eventer
|
||||
switch strings.ToUpper(options.EventerType) {
|
||||
case strings.ToUpper(Journald.String()):
|
||||
eventer = EventJournalD{options}
|
||||
case strings.ToUpper(LogFile.String()):
|
||||
eventer = EventLogFile{options}
|
||||
default:
|
||||
return eventer, errors.Errorf("unknown event logger type: %s", strings.ToUpper(options.EventerType))
|
||||
}
|
||||
return eventer, nil
|
||||
}
|
10
libpod/events/events_unsupported.go
Normal file
10
libpod/events/events_unsupported.go
Normal file
@ -0,0 +1,10 @@
|
||||
// +build !linux
|
||||
|
||||
package events
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
// NewEventer creates an eventer based on the eventer type
|
||||
func NewEventer(options EventerOptions) (Eventer, error) {
|
||||
return nil, errors.New("this function is not available for your platform")
|
||||
}
|
@ -1,20 +1,19 @@
|
||||
package shared
|
||||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/libpod/libpod/events"
|
||||
"github.com/containers/libpod/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool, error) {
|
||||
func generateEventFilter(filter, filterValue string) (func(e *Event) bool, error) {
|
||||
switch strings.ToUpper(filter) {
|
||||
case "CONTAINER":
|
||||
return func(e *events.Event) bool {
|
||||
if e.Type != events.Container {
|
||||
return func(e *Event) bool {
|
||||
if e.Type != Container {
|
||||
return false
|
||||
}
|
||||
if e.Name == filterValue {
|
||||
@ -23,12 +22,12 @@ func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool
|
||||
return strings.HasPrefix(e.ID, filterValue)
|
||||
}, nil
|
||||
case "EVENT", "STATUS":
|
||||
return func(e *events.Event) bool {
|
||||
return func(e *Event) bool {
|
||||
return fmt.Sprintf("%s", e.Status) == filterValue
|
||||
}, nil
|
||||
case "IMAGE":
|
||||
return func(e *events.Event) bool {
|
||||
if e.Type != events.Image {
|
||||
return func(e *Event) bool {
|
||||
if e.Type != Image {
|
||||
return false
|
||||
}
|
||||
if e.Name == filterValue {
|
||||
@ -37,8 +36,8 @@ func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool
|
||||
return strings.HasPrefix(e.ID, filterValue)
|
||||
}, nil
|
||||
case "POD":
|
||||
return func(e *events.Event) bool {
|
||||
if e.Type != events.Pod {
|
||||
return func(e *Event) bool {
|
||||
if e.Type != Pod {
|
||||
return false
|
||||
}
|
||||
if e.Name == filterValue {
|
||||
@ -47,28 +46,28 @@ func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool
|
||||
return strings.HasPrefix(e.ID, filterValue)
|
||||
}, nil
|
||||
case "VOLUME":
|
||||
return func(e *events.Event) bool {
|
||||
if e.Type != events.Volume {
|
||||
return func(e *Event) bool {
|
||||
if e.Type != Volume {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(e.ID, filterValue)
|
||||
}, nil
|
||||
case "TYPE":
|
||||
return func(e *events.Event) bool {
|
||||
return func(e *Event) bool {
|
||||
return fmt.Sprintf("%s", e.Type) == filterValue
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.Errorf("%s is an invalid filter", filter)
|
||||
}
|
||||
|
||||
func generateEventSinceOption(timeSince time.Time) func(e *events.Event) bool {
|
||||
return func(e *events.Event) bool {
|
||||
func generateEventSinceOption(timeSince time.Time) func(e *Event) bool {
|
||||
return func(e *Event) bool {
|
||||
return e.Time.After(timeSince)
|
||||
}
|
||||
}
|
||||
|
||||
func generateEventUntilOption(timeUntil time.Time) func(e *events.Event) bool {
|
||||
return func(e *events.Event) bool {
|
||||
func generateEventUntilOption(timeUntil time.Time) func(e *Event) bool {
|
||||
return func(e *Event) bool {
|
||||
return e.Time.Before(timeUntil)
|
||||
|
||||
}
|
||||
@ -82,8 +81,8 @@ func parseFilter(filter string) (string, string, error) {
|
||||
return filterSplit[0], filterSplit[1], nil
|
||||
}
|
||||
|
||||
func GenerateEventOptions(filters []string, since, until string) ([]events.EventFilter, error) {
|
||||
var options []events.EventFilter
|
||||
func generateEventOptions(filters []string, since, until string) ([]EventFilter, error) {
|
||||
var options []EventFilter
|
||||
for _, filter := range filters {
|
||||
key, val, err := parseFilter(filter)
|
||||
if err != nil {
|
131
libpod/events/journal_linux.go
Normal file
131
libpod/events/journal_linux.go
Normal file
@ -0,0 +1,131 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-systemd/journal"
|
||||
"github.com/coreos/go-systemd/sdjournal"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// EventJournalD is the journald implementation of an eventer
|
||||
type EventJournalD struct {
|
||||
options EventerOptions
|
||||
}
|
||||
|
||||
// Write to journald
|
||||
func (e EventJournalD) Write(ee Event) error {
|
||||
m := make(map[string]string)
|
||||
m["SYSLOG_IDENTIFIER"] = "podman"
|
||||
m["PODMAN_EVENT"] = ee.Status.String()
|
||||
m["PODMAN_TYPE"] = ee.Type.String()
|
||||
m["PODMAN_TIME"] = ee.Time.Format(time.RFC3339Nano)
|
||||
|
||||
// Add specialized information based on the podman type
|
||||
switch ee.Type {
|
||||
case Image:
|
||||
m["PODMAN_NAME"] = ee.Name
|
||||
m["PODMAN_ID"] = ee.ID
|
||||
case Container, Pod:
|
||||
m["PODMAN_IMAGE"] = ee.Image
|
||||
m["PODMAN_NAME"] = ee.Name
|
||||
m["PODMAN_ID"] = ee.ID
|
||||
case Volume:
|
||||
m["PODMAN_NAME"] = ee.Name
|
||||
}
|
||||
return journal.Send(fmt.Sprintf("%s", ee.ToHumanReadable()), journal.PriInfo, m)
|
||||
}
|
||||
|
||||
// Read reads events from the journal and sends qualified events to the event channel
|
||||
func (e EventJournalD) Read(options ReadOptions) error {
|
||||
eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to generate event options")
|
||||
}
|
||||
podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} //nolint
|
||||
j, err := sdjournal.NewJournal() //nolint
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := j.AddMatch(podmanJournal.String()); err != nil {
|
||||
return errors.Wrap(err, "failed to add filter for event log")
|
||||
}
|
||||
if len(options.Since) == 0 && len(options.Until) == 0 && options.Stream {
|
||||
if err := j.SeekTail(); err != nil {
|
||||
return errors.Wrap(err, "failed to seek end of journal")
|
||||
}
|
||||
}
|
||||
// the api requires a next|prev before getting a cursor
|
||||
if _, err := j.Next(); err != nil {
|
||||
return err
|
||||
}
|
||||
prevCursor, err := j.GetCursor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer close(options.EventChannel)
|
||||
for {
|
||||
if _, err := j.Next(); err != nil {
|
||||
return err
|
||||
}
|
||||
newCursor, err := j.GetCursor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if prevCursor == newCursor {
|
||||
if len(options.Until) > 0 || !options.Stream {
|
||||
break
|
||||
}
|
||||
_ = j.Wait(sdjournal.IndefiniteWait) //nolint
|
||||
continue
|
||||
}
|
||||
prevCursor = newCursor
|
||||
entry, err := j.GetEntry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newEvent, err := newEventFromJournalEntry(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
include := true
|
||||
for _, filter := range eventOptions {
|
||||
include = include && filter(newEvent)
|
||||
}
|
||||
if include {
|
||||
options.EventChannel <- newEvent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { //nolint
|
||||
newEvent := Event{}
|
||||
eventType, err := StringToType(entry.Fields["PODMAN_TYPE"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eventTime, err := time.Parse(time.RFC3339Nano, entry.Fields["PODMAN_TIME"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eventStatus, err := StringToStatus(entry.Fields["PODMAN_EVENT"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newEvent.Type = eventType
|
||||
newEvent.Time = eventTime
|
||||
newEvent.Status = eventStatus
|
||||
newEvent.Name = entry.Fields["PODMAN_NAME"]
|
||||
|
||||
switch eventType {
|
||||
case Container, Pod:
|
||||
newEvent.ID = entry.Fields["PODMAN_ID"]
|
||||
newEvent.Image = entry.Fields["PODMAN_IMAGE"]
|
||||
case Image:
|
||||
newEvent.ID = entry.Fields["PODMAN_ID"]
|
||||
}
|
||||
return &newEvent, nil
|
||||
}
|
65
libpod/events/logfile.go
Normal file
65
libpod/events/logfile.go
Normal file
@ -0,0 +1,65 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// EventLogFile is the structure for event writing to a logfile. It contains the eventer
|
||||
// options and the event itself. Methods for reading and writing are also defined from it.
|
||||
type EventLogFile struct {
|
||||
options EventerOptions
|
||||
}
|
||||
|
||||
// Writes to the log file
|
||||
func (e EventLogFile) Write(ee Event) error {
|
||||
f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
eventJSONString, err := ee.ToJSONString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Reads from the log file
|
||||
func (e EventLogFile) Read(options ReadOptions) error {
|
||||
eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to generate event options")
|
||||
}
|
||||
t, err := e.getTail(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for line := range t.Lines {
|
||||
event, err := newEventFromJSONString(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch event.Type {
|
||||
case Image, Volume, Pod, Container:
|
||||
// no-op
|
||||
default:
|
||||
return errors.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath)
|
||||
}
|
||||
include := true
|
||||
for _, filter := range eventOptions {
|
||||
include = include && filter(event)
|
||||
}
|
||||
if include {
|
||||
options.EventChannel <- event
|
||||
}
|
||||
}
|
||||
close(options.EventChannel)
|
||||
return nil
|
||||
}
|
23
libpod/events/nullout.go
Normal file
23
libpod/events/nullout.go
Normal file
@ -0,0 +1,23 @@
|
||||
package events
|
||||
|
||||
// EventToNull is an eventer type that only performs write operations
|
||||
// and only writes to /dev/null. It is meant for unittests only
|
||||
type EventToNull struct{}
|
||||
|
||||
// Write eats the event and always returns nil
|
||||
func (e EventToNull) Write(ee Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read does nothing. Do not use it.
|
||||
func (e EventToNull) Read(options ReadOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewNullEventer returns a new null eventer. You should only do this for
|
||||
// the purposes on internal libpod testing.
|
||||
func NewNullEventer() Eventer {
|
||||
var e Eventer
|
||||
e = EventToNull{}
|
||||
return e
|
||||
}
|
@ -66,6 +66,8 @@ type Runtime struct {
|
||||
store storage.Store
|
||||
SignaturePolicyPath string
|
||||
EventsLogFilePath string
|
||||
EventsLogger string
|
||||
Eventer events.Eventer
|
||||
}
|
||||
|
||||
// InfoImage keep information of Image along with all associated layers
|
||||
@ -1203,7 +1205,7 @@ func (ir *Runtime) newImageEvent(status events.Status, name string) {
|
||||
e := events.NewEvent(status)
|
||||
e.Type = events.Image
|
||||
e.Name = name
|
||||
if err := e.Write(ir.EventsLogFilePath); err != nil {
|
||||
if err := ir.Eventer.Write(e); err != nil {
|
||||
logrus.Infof("unable to write event to %s", ir.EventsLogFilePath)
|
||||
}
|
||||
}
|
||||
@ -1216,7 +1218,7 @@ func (i *Image) newImageEvent(status events.Status) {
|
||||
if len(i.Names()) > 0 {
|
||||
e.Name = i.Names()[0]
|
||||
}
|
||||
if err := e.Write(i.imageruntime.EventsLogFilePath); err != nil {
|
||||
if err := i.imageruntime.Eventer.Write(e); err != nil {
|
||||
logrus.Infof("unable to write event to %s", i.imageruntime.EventsLogFilePath)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package image
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/containers/libpod/libpod/events"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -87,6 +88,7 @@ func TestImage_NewFromLocal(t *testing.T) {
|
||||
// Need images to be present for this test
|
||||
ir, err := NewImageRuntimeFromOptions(so)
|
||||
assert.NoError(t, err)
|
||||
ir.Eventer = events.NewNullEventer()
|
||||
bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, false, nil)
|
||||
assert.NoError(t, err)
|
||||
bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, false, nil)
|
||||
@ -127,6 +129,7 @@ func TestImage_New(t *testing.T) {
|
||||
}
|
||||
ir, err := NewImageRuntimeFromOptions(so)
|
||||
assert.NoError(t, err)
|
||||
ir.Eventer = events.NewNullEventer()
|
||||
// Build the list of pull names
|
||||
names = append(names, bbNames...)
|
||||
names = append(names, fedoraNames...)
|
||||
@ -164,6 +167,7 @@ func TestImage_MatchRepoTag(t *testing.T) {
|
||||
}
|
||||
ir, err := NewImageRuntimeFromOptions(so)
|
||||
assert.NoError(t, err)
|
||||
ir.Eventer = events.NewNullEventer()
|
||||
newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, false, nil)
|
||||
assert.NoError(t, err)
|
||||
err = newImage.TagImage("foo:latest")
|
||||
|
@ -2,6 +2,7 @@ package libpod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containers/libpod/libpod/events"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -105,6 +106,9 @@ type Runtime struct {
|
||||
// storage unusable). When valid is false, the runtime cannot be used.
|
||||
valid bool
|
||||
lock sync.RWMutex
|
||||
|
||||
// mechanism to read and write even logs
|
||||
eventer events.Eventer
|
||||
}
|
||||
|
||||
// OCIRuntimePath contains information about an OCI runtime.
|
||||
@ -222,6 +226,8 @@ type RuntimeConfig struct {
|
||||
// pods.
|
||||
NumLocks uint32 `toml:"num_locks,omitempty"`
|
||||
|
||||
// EventsLogger determines where events should be logged
|
||||
EventsLogger string `toml:"events_logger"`
|
||||
// EventsLogFilePath is where the events log is stored.
|
||||
EventsLogFilePath string `toml:-"events_logfile_path"`
|
||||
}
|
||||
@ -252,7 +258,6 @@ func defaultRuntimeConfig() (RuntimeConfig, error) {
|
||||
if err != nil {
|
||||
return RuntimeConfig{}, err
|
||||
}
|
||||
|
||||
return RuntimeConfig{
|
||||
// Leave this empty so containers/storage will use its defaults
|
||||
StorageConfig: storage.StoreOptions{},
|
||||
@ -296,6 +301,7 @@ func defaultRuntimeConfig() (RuntimeConfig, error) {
|
||||
EnablePortReservation: true,
|
||||
EnableLabeling: true,
|
||||
NumLocks: 2048,
|
||||
EventsLogger: "journald",
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -755,16 +761,24 @@ func makeRuntime(runtime *Runtime) (err error) {
|
||||
|
||||
// Set up image runtime and store in runtime
|
||||
ir := image.NewImageRuntimeFromStore(runtime.store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runtime.imageRuntime = ir
|
||||
|
||||
// Setting signaturepolicypath
|
||||
ir.SignaturePolicyPath = runtime.config.SignaturePolicyPath
|
||||
|
||||
// Set logfile path for events
|
||||
ir.EventsLogFilePath = runtime.config.EventsLogFilePath
|
||||
// Set logger type
|
||||
ir.EventsLogger = runtime.config.EventsLogger
|
||||
|
||||
// Setup the eventer
|
||||
eventer, err := runtime.newEventer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.eventer = eventer
|
||||
ir.Eventer = eventer
|
||||
|
||||
defer func() {
|
||||
if err != nil && store != nil {
|
||||
|
@ -5,6 +5,7 @@ package adapter
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"github.com/containers/libpod/cmd/podman/shared"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -17,7 +18,6 @@ import (
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||
"github.com/containers/libpod/cmd/podman/libpodruntime"
|
||||
"github.com/containers/libpod/cmd/podman/shared"
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/libpod/events"
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
@ -322,10 +322,6 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error {
|
||||
fromStart bool
|
||||
eventsError error
|
||||
)
|
||||
options, err := shared.GenerateEventOptions(c.Filter, c.Since, c.Until)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to generate event options")
|
||||
}
|
||||
tmpl, err := template.New("events").Parse(c.Format)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -335,7 +331,8 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error {
|
||||
}
|
||||
eventChannel := make(chan *events.Event)
|
||||
go func() {
|
||||
eventsError = r.Runtime.Events(fromStart, c.Stream, options, eventChannel)
|
||||
readOpts := events.ReadOptions{FromStart: fromStart, Stream: c.Stream, Filters: c.Filter, EventChannel: eventChannel, Since: c.Since, Until: c.Until}
|
||||
eventsError = r.Runtime.Events(readOpts)
|
||||
}()
|
||||
|
||||
if eventsError != nil {
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/containers/libpod/cmd/podman/shared"
|
||||
"github.com/containers/libpod/cmd/podman/varlink"
|
||||
"github.com/containers/libpod/libpod/events"
|
||||
)
|
||||
@ -23,19 +22,16 @@ func (i *LibpodAPI) GetEvents(call iopodman.VarlinkCall, filter []string, since
|
||||
stream = true
|
||||
call.Continues = true
|
||||
}
|
||||
filters, err := shared.GenerateEventOptions(filter, since, until)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
if len(since) > 0 || len(until) > 0 {
|
||||
fromStart = true
|
||||
}
|
||||
eventChannel := make(chan *events.Event)
|
||||
go func() {
|
||||
eventsError = i.Runtime.Events(fromStart, stream, filters, eventChannel)
|
||||
readOpts := events.ReadOptions{FromStart: fromStart, Stream: stream, Filters: filter, EventChannel: eventChannel}
|
||||
eventsError = i.Runtime.Events(readOpts)
|
||||
}()
|
||||
if eventsError != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
return call.ReplyErrorOccurred(eventsError.Error())
|
||||
}
|
||||
for {
|
||||
event = <-eventChannel
|
||||
|
@ -407,9 +407,13 @@ func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegrat
|
||||
func (p *PodmanTestIntegration) Cleanup() {
|
||||
// Remove all containers
|
||||
stopall := p.Podman([]string{"stop", "-a", "--timeout", "0"})
|
||||
// stopall.WaitWithDefaultTimeout()
|
||||
stopall.Wait(90)
|
||||
|
||||
podstop := p.Podman([]string{"pod", "stop", "-a", "-t", "0"})
|
||||
podstop.WaitWithDefaultTimeout()
|
||||
podrm := p.Podman([]string{"pod", "rm", "-fa"})
|
||||
podrm.WaitWithDefaultTimeout()
|
||||
|
||||
session := p.Podman([]string{"rm", "-fa"})
|
||||
session.Wait(90)
|
||||
|
||||
|
@ -39,6 +39,7 @@ var _ = Describe("Podman events", func() {
|
||||
// Perhaps a future version of this test would put events in a go func and send output back over a channel
|
||||
// while events occur.
|
||||
It("podman events", func() {
|
||||
Skip("need to verify images have correct packages for journald")
|
||||
_, ec, _ := podmanTest.RunLsContainer("")
|
||||
Expect(ec).To(Equal(0))
|
||||
result := podmanTest.Podman([]string{"events", "--stream=false"})
|
||||
@ -47,17 +48,17 @@ var _ = Describe("Podman events", func() {
|
||||
})
|
||||
|
||||
It("podman events with an event filter", func() {
|
||||
SkipIfRemote()
|
||||
Skip("need to verify images have correct packages for journald")
|
||||
_, ec, _ := podmanTest.RunLsContainer("")
|
||||
Expect(ec).To(Equal(0))
|
||||
result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=start"})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result.ExitCode()).To(Equal(0))
|
||||
Expect(len(result.OutputToStringArray())).To(Equal(1))
|
||||
Expect(len(result.OutputToStringArray()) >= 1)
|
||||
})
|
||||
|
||||
It("podman events with an event filter and container=cid", func() {
|
||||
SkipIfRemote()
|
||||
Skip("need to verify images have correct packages for journald")
|
||||
_, ec, cid := podmanTest.RunLsContainer("")
|
||||
Expect(ec).To(Equal(0))
|
||||
_, ec2, cid2 := podmanTest.RunLsContainer("")
|
||||
@ -69,32 +70,33 @@ var _ = Describe("Podman events", func() {
|
||||
Expect(!strings.Contains(result.OutputToString(), cid2))
|
||||
})
|
||||
|
||||
It("podman events with a type", func() {
|
||||
SkipIfRemote()
|
||||
_, ec, _ := podmanTest.RunLsContainer("")
|
||||
It("podman events with a type and filter container=id", func() {
|
||||
Skip("need to verify images have correct packages for journald")
|
||||
_, ec, cid := podmanTest.RunLsContainer("")
|
||||
Expect(ec).To(Equal(0))
|
||||
result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod"})
|
||||
result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod", "--filter", fmt.Sprintf("container=%s", cid)})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result.ExitCode()).To(Equal(0))
|
||||
Expect(len(result.OutputToStringArray())).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman events with a type", func() {
|
||||
SkipIfRemote()
|
||||
setup := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:foobar", ALPINE, "top"})
|
||||
Skip("need to verify images have correct packages for journald")
|
||||
setup := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:foobarpod", ALPINE, "top"})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
stop := podmanTest.Podman([]string{"pod", "stop", "foobar"})
|
||||
stop := podmanTest.Podman([]string{"pod", "stop", "foobarpod"})
|
||||
stop.WaitWithDefaultTimeout()
|
||||
Expect(stop.ExitCode()).To(Equal(0))
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod"})
|
||||
result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod", "--filter", "pod=foobarpod"})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result.ExitCode()).To(Equal(0))
|
||||
fmt.Println(result.OutputToStringArray())
|
||||
Expect(len(result.OutputToStringArray())).To(Equal(2))
|
||||
Expect(len(result.OutputToStringArray()) >= 2)
|
||||
})
|
||||
|
||||
It("podman events --since", func() {
|
||||
Skip("need to verify images have correct packages for journald")
|
||||
_, ec, _ := podmanTest.RunLsContainer("")
|
||||
Expect(ec).To(Equal(0))
|
||||
result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1m"})
|
||||
@ -103,6 +105,7 @@ var _ = Describe("Podman events", func() {
|
||||
})
|
||||
|
||||
It("podman events --until", func() {
|
||||
Skip("need to verify images have correct packages for journald")
|
||||
_, ec, _ := podmanTest.RunLsContainer("")
|
||||
Expect(ec).To(Equal(0))
|
||||
test := podmanTest.Podman([]string{"events", "--help"})
|
||||
|
@ -22,6 +22,7 @@ github.com/VividCortex/ewma v1.1.1
|
||||
github.com/containers/storage v1.12.1
|
||||
github.com/containers/psgo v1.2
|
||||
github.com/coreos/go-systemd v14
|
||||
github.com/coreos/pkg v4
|
||||
github.com/cri-o/ocicni 0c180f981b27ef6036fa5be29bcb4dd666e406eb
|
||||
github.com/cyphar/filepath-securejoin v0.2.1
|
||||
github.com/davecgh/go-spew v1.1.0
|
||||
|
179
vendor/github.com/coreos/go-systemd/journal/journal.go
generated
vendored
Normal file
179
vendor/github.com/coreos/go-systemd/journal/journal.go
generated
vendored
Normal file
@ -0,0 +1,179 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package journal provides write bindings to the local systemd journal.
|
||||
// It is implemented in pure Go and connects to the journal directly over its
|
||||
// unix socket.
|
||||
//
|
||||
// To read from the journal, see the "sdjournal" package, which wraps the
|
||||
// sd-journal a C API.
|
||||
//
|
||||
// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
|
||||
package journal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Priority of a journal message
|
||||
type Priority int
|
||||
|
||||
const (
|
||||
PriEmerg Priority = iota
|
||||
PriAlert
|
||||
PriCrit
|
||||
PriErr
|
||||
PriWarning
|
||||
PriNotice
|
||||
PriInfo
|
||||
PriDebug
|
||||
)
|
||||
|
||||
var conn net.Conn
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
|
||||
if err != nil {
|
||||
conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled returns true if the local systemd journal is available for logging
|
||||
func Enabled() bool {
|
||||
return conn != nil
|
||||
}
|
||||
|
||||
// Send a message to the local systemd journal. vars is a map of journald
|
||||
// fields to values. Fields must be composed of uppercase letters, numbers,
|
||||
// and underscores, but must not start with an underscore. Within these
|
||||
// restrictions, any arbitrary field name may be used. Some names have special
|
||||
// significance: see the journalctl documentation
|
||||
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
|
||||
// for more details. vars may be nil.
|
||||
func Send(message string, priority Priority, vars map[string]string) error {
|
||||
if conn == nil {
|
||||
return journalError("could not connect to journald socket")
|
||||
}
|
||||
|
||||
data := new(bytes.Buffer)
|
||||
appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
|
||||
appendVariable(data, "MESSAGE", message)
|
||||
for k, v := range vars {
|
||||
appendVariable(data, k, v)
|
||||
}
|
||||
|
||||
_, err := io.Copy(conn, data)
|
||||
if err != nil && isSocketSpaceError(err) {
|
||||
file, err := tempFd()
|
||||
if err != nil {
|
||||
return journalError(err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(file, data)
|
||||
if err != nil {
|
||||
return journalError(err.Error())
|
||||
}
|
||||
|
||||
rights := syscall.UnixRights(int(file.Fd()))
|
||||
|
||||
/* this connection should always be a UnixConn, but better safe than sorry */
|
||||
unixConn, ok := conn.(*net.UnixConn)
|
||||
if !ok {
|
||||
return journalError("can't send file through non-Unix connection")
|
||||
}
|
||||
unixConn.WriteMsgUnix([]byte{}, rights, nil)
|
||||
} else if err != nil {
|
||||
return journalError(err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print prints a message to the local systemd journal using Send().
|
||||
func Print(priority Priority, format string, a ...interface{}) error {
|
||||
return Send(fmt.Sprintf(format, a...), priority, nil)
|
||||
}
|
||||
|
||||
func appendVariable(w io.Writer, name, value string) {
|
||||
if !validVarName(name) {
|
||||
journalError("variable name contains invalid character, ignoring")
|
||||
}
|
||||
if strings.ContainsRune(value, '\n') {
|
||||
/* When the value contains a newline, we write:
|
||||
* - the variable name, followed by a newline
|
||||
* - the size (in 64bit little endian format)
|
||||
* - the data, followed by a newline
|
||||
*/
|
||||
fmt.Fprintln(w, name)
|
||||
binary.Write(w, binary.LittleEndian, uint64(len(value)))
|
||||
fmt.Fprintln(w, value)
|
||||
} else {
|
||||
/* just write the variable and value all on one line */
|
||||
fmt.Fprintf(w, "%s=%s\n", name, value)
|
||||
}
|
||||
}
|
||||
|
||||
func validVarName(name string) bool {
|
||||
/* The variable name must be in uppercase and consist only of characters,
|
||||
* numbers and underscores, and may not begin with an underscore. (from the docs)
|
||||
*/
|
||||
|
||||
valid := name[0] != '_'
|
||||
for _, c := range name {
|
||||
valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_'
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
func isSocketSpaceError(err error) bool {
|
||||
opErr, ok := err.(*net.OpError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
sysErr, ok := opErr.Err.(syscall.Errno)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
|
||||
}
|
||||
|
||||
func tempFd() (*os.File, error) {
|
||||
file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
syscall.Unlink(file.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func journalError(s string) error {
|
||||
s = "journal error: " + s
|
||||
fmt.Fprintln(os.Stderr, s)
|
||||
return errors.New(s)
|
||||
}
|
66
vendor/github.com/coreos/go-systemd/sdjournal/functions.go
generated
vendored
Normal file
66
vendor/github.com/coreos/go-systemd/sdjournal/functions.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2015 RedHat, Inc.
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sdjournal
|
||||
|
||||
import (
|
||||
"github.com/coreos/pkg/dlopen"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
// lazy initialized
|
||||
libsystemdHandle *dlopen.LibHandle
|
||||
|
||||
libsystemdMutex = &sync.Mutex{}
|
||||
libsystemdFunctions = map[string]unsafe.Pointer{}
|
||||
libsystemdNames = []string{
|
||||
// systemd < 209
|
||||
"libsystemd-journal.so.0",
|
||||
"libsystemd-journal.so",
|
||||
|
||||
// systemd >= 209 merged libsystemd-journal into libsystemd proper
|
||||
"libsystemd.so.0",
|
||||
"libsystemd.so",
|
||||
}
|
||||
)
|
||||
|
||||
func getFunction(name string) (unsafe.Pointer, error) {
|
||||
libsystemdMutex.Lock()
|
||||
defer libsystemdMutex.Unlock()
|
||||
|
||||
if libsystemdHandle == nil {
|
||||
h, err := dlopen.GetHandle(libsystemdNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
libsystemdHandle = h
|
||||
}
|
||||
|
||||
f, ok := libsystemdFunctions[name]
|
||||
if !ok {
|
||||
var err error
|
||||
f, err = libsystemdHandle.GetSymbolPointer(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
libsystemdFunctions[name] = f
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
1024
vendor/github.com/coreos/go-systemd/sdjournal/journal.go
generated
vendored
Normal file
1024
vendor/github.com/coreos/go-systemd/sdjournal/journal.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
260
vendor/github.com/coreos/go-systemd/sdjournal/read.go
generated
vendored
Normal file
260
vendor/github.com/coreos/go-systemd/sdjournal/read.go
generated
vendored
Normal file
@ -0,0 +1,260 @@
|
||||
// Copyright 2015 RedHat, Inc.
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sdjournal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrExpired = errors.New("Timeout expired")
|
||||
)
|
||||
|
||||
// JournalReaderConfig represents options to drive the behavior of a JournalReader.
|
||||
type JournalReaderConfig struct {
|
||||
// The Since, NumFromTail and Cursor options are mutually exclusive and
|
||||
// determine where the reading begins within the journal. The order in which
|
||||
// options are written is exactly the order of precedence.
|
||||
Since time.Duration // start relative to a Duration from now
|
||||
NumFromTail uint64 // start relative to the tail
|
||||
Cursor string // start relative to the cursor
|
||||
|
||||
// Show only journal entries whose fields match the supplied values. If
|
||||
// the array is empty, entries will not be filtered.
|
||||
Matches []Match
|
||||
|
||||
// If not empty, the journal instance will point to a journal residing
|
||||
// in this directory. The supplied path may be relative or absolute.
|
||||
Path string
|
||||
}
|
||||
|
||||
// JournalReader is an io.ReadCloser which provides a simple interface for iterating through the
|
||||
// systemd journal. A JournalReader is not safe for concurrent use by multiple goroutines.
|
||||
type JournalReader struct {
|
||||
journal *Journal
|
||||
msgReader *strings.Reader
|
||||
}
|
||||
|
||||
// NewJournalReader creates a new JournalReader with configuration options that are similar to the
|
||||
// systemd journalctl tool's iteration and filtering features.
|
||||
func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) {
|
||||
r := &JournalReader{}
|
||||
|
||||
// Open the journal
|
||||
var err error
|
||||
if config.Path != "" {
|
||||
r.journal, err = NewJournalFromDir(config.Path)
|
||||
} else {
|
||||
r.journal, err = NewJournal()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add any supplied matches
|
||||
for _, m := range config.Matches {
|
||||
r.journal.AddMatch(m.String())
|
||||
}
|
||||
|
||||
// Set the start position based on options
|
||||
if config.Since != 0 {
|
||||
// Start based on a relative time
|
||||
start := time.Now().Add(config.Since)
|
||||
if err := r.journal.SeekRealtimeUsec(uint64(start.UnixNano() / 1000)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if config.NumFromTail != 0 {
|
||||
// Start based on a number of lines before the tail
|
||||
if err := r.journal.SeekTail(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Move the read pointer into position near the tail. Go one further than
|
||||
// the option so that the initial cursor advancement positions us at the
|
||||
// correct starting point.
|
||||
skip, err := r.journal.PreviousSkip(config.NumFromTail + 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If we skipped fewer lines than expected, we have reached journal start.
|
||||
// Thus, we seek to head so that next invocation can read the first line.
|
||||
if skip != config.NumFromTail+1 {
|
||||
if err := r.journal.SeekHead(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if config.Cursor != "" {
|
||||
// Start based on a custom cursor
|
||||
if err := r.journal.SeekCursor(config.Cursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Read reads entries from the journal. Read follows the Reader interface so
|
||||
// it must be able to read a specific amount of bytes. Journald on the other
|
||||
// hand only allows us to read full entries of arbitrary size (without byte
|
||||
// granularity). JournalReader is therefore internally buffering entries that
|
||||
// don't fit in the read buffer. Callers should keep calling until 0 and/or an
|
||||
// error is returned.
|
||||
func (r *JournalReader) Read(b []byte) (int, error) {
|
||||
var err error
|
||||
|
||||
if r.msgReader == nil {
|
||||
var c uint64
|
||||
|
||||
// Advance the journal cursor. It has to be called at least one time
|
||||
// before reading
|
||||
c, err = r.journal.Next()
|
||||
|
||||
// An unexpected error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// EOF detection
|
||||
if c == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// Build a message
|
||||
var msg string
|
||||
msg, err = r.buildMessage()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.msgReader = strings.NewReader(msg)
|
||||
}
|
||||
|
||||
// Copy and return the message
|
||||
var sz int
|
||||
sz, err = r.msgReader.Read(b)
|
||||
if err == io.EOF {
|
||||
// The current entry has been fully read. Don't propagate this
|
||||
// EOF, so the next entry can be read at the next Read()
|
||||
// iteration.
|
||||
r.msgReader = nil
|
||||
return sz, nil
|
||||
}
|
||||
if err != nil {
|
||||
return sz, err
|
||||
}
|
||||
if r.msgReader.Len() == 0 {
|
||||
r.msgReader = nil
|
||||
}
|
||||
|
||||
return sz, nil
|
||||
}
|
||||
|
||||
// Close closes the JournalReader's handle to the journal.
|
||||
func (r *JournalReader) Close() error {
|
||||
return r.journal.Close()
|
||||
}
|
||||
|
||||
// Rewind attempts to rewind the JournalReader to the first entry.
|
||||
func (r *JournalReader) Rewind() error {
|
||||
r.msgReader = nil
|
||||
return r.journal.SeekHead()
|
||||
}
|
||||
|
||||
// Follow synchronously follows the JournalReader, writing each new journal entry to writer. The
|
||||
// follow will continue until a single time.Time is received on the until channel.
|
||||
func (r *JournalReader) Follow(until <-chan time.Time, writer io.Writer) (err error) {
|
||||
|
||||
// Process journal entries and events. Entries are flushed until the tail or
|
||||
// timeout is reached, and then we wait for new events or the timeout.
|
||||
var msg = make([]byte, 64*1<<(10))
|
||||
process:
|
||||
for {
|
||||
c, err := r.Read(msg)
|
||||
if err != nil && err != io.EOF {
|
||||
break process
|
||||
}
|
||||
|
||||
select {
|
||||
case <-until:
|
||||
return ErrExpired
|
||||
default:
|
||||
if c > 0 {
|
||||
if _, err = writer.Write(msg[:c]); err != nil {
|
||||
break process
|
||||
}
|
||||
continue process
|
||||
}
|
||||
}
|
||||
|
||||
// We're at the tail, so wait for new events or time out.
|
||||
// Holds journal events to process. Tightly bounded for now unless there's a
|
||||
// reason to unblock the journal watch routine more quickly.
|
||||
events := make(chan int, 1)
|
||||
pollDone := make(chan bool, 1)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-pollDone:
|
||||
return
|
||||
default:
|
||||
events <- r.journal.Wait(time.Duration(1) * time.Second)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-until:
|
||||
pollDone <- true
|
||||
return ErrExpired
|
||||
case e := <-events:
|
||||
pollDone <- true
|
||||
switch e {
|
||||
case SD_JOURNAL_NOP, SD_JOURNAL_APPEND, SD_JOURNAL_INVALIDATE:
|
||||
// TODO: need to account for any of these?
|
||||
default:
|
||||
log.Printf("Received unknown event: %d\n", e)
|
||||
}
|
||||
continue process
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// buildMessage returns a string representing the current journal entry in a simple format which
|
||||
// includes the entry timestamp and MESSAGE field.
|
||||
func (r *JournalReader) buildMessage() (string, error) {
|
||||
var msg string
|
||||
var usec uint64
|
||||
var err error
|
||||
|
||||
if msg, err = r.journal.GetData("MESSAGE"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if usec, err = r.journal.GetRealtimeUsec(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond))
|
||||
|
||||
return fmt.Sprintf("%s %s\n", timestamp, msg), nil
|
||||
}
|
202
vendor/github.com/coreos/pkg/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/pkg/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
5
vendor/github.com/coreos/pkg/NOTICE
generated
vendored
Normal file
5
vendor/github.com/coreos/pkg/NOTICE
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
CoreOS Project
|
||||
Copyright 2014 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
4
vendor/github.com/coreos/pkg/README.md
generated
vendored
Normal file
4
vendor/github.com/coreos/pkg/README.md
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
a collection of go utility packages
|
||||
|
||||
[](https://travis-ci.org/coreos/pkg)
|
||||
[](https://godoc.org/github.com/coreos/pkg)
|
82
vendor/github.com/coreos/pkg/dlopen/dlopen.go
generated
vendored
Normal file
82
vendor/github.com/coreos/pkg/dlopen/dlopen.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package dlopen provides some convenience functions to dlopen a library and
|
||||
// get its symbols.
|
||||
package dlopen
|
||||
|
||||
// #cgo LDFLAGS: -ldl
|
||||
// #include <stdlib.h>
|
||||
// #include <dlfcn.h>
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var ErrSoNotFound = errors.New("unable to open a handle to the library")
|
||||
|
||||
// LibHandle represents an open handle to a library (.so)
|
||||
type LibHandle struct {
|
||||
Handle unsafe.Pointer
|
||||
Libname string
|
||||
}
|
||||
|
||||
// GetHandle tries to get a handle to a library (.so), attempting to access it
|
||||
// by the names specified in libs and returning the first that is successfully
|
||||
// opened. Callers are responsible for closing the handler. If no library can
|
||||
// be successfully opened, an error is returned.
|
||||
func GetHandle(libs []string) (*LibHandle, error) {
|
||||
for _, name := range libs {
|
||||
libname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(libname))
|
||||
handle := C.dlopen(libname, C.RTLD_LAZY)
|
||||
if handle != nil {
|
||||
h := &LibHandle{
|
||||
Handle: handle,
|
||||
Libname: name,
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrSoNotFound
|
||||
}
|
||||
|
||||
// GetSymbolPointer takes a symbol name and returns a pointer to the symbol.
|
||||
func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) {
|
||||
sym := C.CString(symbol)
|
||||
defer C.free(unsafe.Pointer(sym))
|
||||
|
||||
C.dlerror()
|
||||
p := C.dlsym(l.Handle, sym)
|
||||
e := C.dlerror()
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("error resolving symbol %q: %v", symbol, errors.New(C.GoString(e)))
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Close closes a LibHandle.
|
||||
func (l *LibHandle) Close() error {
|
||||
C.dlerror()
|
||||
C.dlclose(l.Handle)
|
||||
e := C.dlerror()
|
||||
if e != nil {
|
||||
return fmt.Errorf("error closing %v: %v", l.Libname, errors.New(C.GoString(e)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
56
vendor/github.com/coreos/pkg/dlopen/dlopen_example.go
generated
vendored
Normal file
56
vendor/github.com/coreos/pkg/dlopen/dlopen_example.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// +build linux
|
||||
|
||||
package dlopen
|
||||
|
||||
// #include <string.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
// int
|
||||
// my_strlen(void *f, const char *s)
|
||||
// {
|
||||
// size_t (*strlen)(const char *);
|
||||
//
|
||||
// strlen = (size_t (*)(const char *))f;
|
||||
// return strlen(s);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func strlen(libs []string, s string) (int, error) {
|
||||
h, err := GetHandle(libs)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf(`couldn't get a handle to the library: %v`, err)
|
||||
}
|
||||
defer h.Close()
|
||||
|
||||
f := "strlen"
|
||||
cs := C.CString(s)
|
||||
defer C.free(unsafe.Pointer(cs))
|
||||
|
||||
strlen, err := h.GetSymbolPointer(f)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf(`couldn't get symbol %q: %v`, f, err)
|
||||
}
|
||||
|
||||
len := C.my_strlen(strlen, cs)
|
||||
|
||||
return int(len), nil
|
||||
}
|
Reference in New Issue
Block a user