mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-19 02:10:35 +02:00
Updated vendor for exported BuildArgs
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
package provider // import "github.com/docker/docker/daemon/cluster/provider"
|
||||
|
||||
import "github.com/docker/docker/api/types"
|
||||
|
||||
// NetworkCreateRequest is a request when creating a network.
|
||||
type NetworkCreateRequest struct {
|
||||
ID string
|
||||
types.NetworkCreateRequest
|
||||
}
|
||||
|
||||
// NetworkCreateResponse is a response when creating a network.
|
||||
type NetworkCreateResponse struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
// VirtualAddress represents a virtual address.
|
||||
type VirtualAddress struct {
|
||||
IPv4 string
|
||||
IPv6 string
|
||||
}
|
||||
|
||||
// PortConfig represents a port configuration.
|
||||
type PortConfig struct {
|
||||
Name string
|
||||
Protocol int32
|
||||
TargetPort uint32
|
||||
PublishedPort uint32
|
||||
}
|
||||
|
||||
// ServiceConfig represents a service configuration.
|
||||
type ServiceConfig struct {
|
||||
ID string
|
||||
Name string
|
||||
Aliases map[string][]string
|
||||
VirtualAddresses map[string]*VirtualAddress
|
||||
ExposedPorts []*PortConfig
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
package exec // import "github.com/docker/docker/daemon/exec"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/docker/docker/container/stream"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Config holds the configurations for execs. The Daemon keeps
|
||||
// track of both running and finished execs so that they can be
|
||||
// examined both during and after completion.
|
||||
type Config struct {
|
||||
sync.Mutex
|
||||
StreamConfig *stream.Config
|
||||
ID string
|
||||
Running bool
|
||||
ExitCode *int
|
||||
OpenStdin bool
|
||||
OpenStderr bool
|
||||
OpenStdout bool
|
||||
CanRemove bool
|
||||
ContainerID string
|
||||
DetachKeys []byte
|
||||
Entrypoint string
|
||||
Args []string
|
||||
Tty bool
|
||||
Privileged bool
|
||||
User string
|
||||
WorkingDir string
|
||||
Env []string
|
||||
Pid int
|
||||
}
|
||||
|
||||
// NewConfig initializes the a new exec configuration
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
ID: stringid.GenerateNonCryptoID(),
|
||||
StreamConfig: stream.NewConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
type rio struct {
|
||||
cio.IO
|
||||
|
||||
sc *stream.Config
|
||||
}
|
||||
|
||||
func (i *rio) Close() error {
|
||||
i.IO.Close()
|
||||
|
||||
return i.sc.CloseStreams()
|
||||
}
|
||||
|
||||
func (i *rio) Wait() {
|
||||
i.sc.Wait()
|
||||
|
||||
i.IO.Wait()
|
||||
}
|
||||
|
||||
// InitializeStdio is called by libcontainerd to connect the stdio.
|
||||
func (c *Config) InitializeStdio(iop *cio.DirectIO) (cio.IO, error) {
|
||||
c.StreamConfig.CopyToPipe(iop)
|
||||
|
||||
if c.StreamConfig.Stdin() == nil && !c.Tty && runtime.GOOS == "windows" {
|
||||
if iop.Stdin != nil {
|
||||
if err := iop.Stdin.Close(); err != nil {
|
||||
logrus.Errorf("error closing exec stdin: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &rio{IO: iop, sc: c.StreamConfig}, nil
|
||||
}
|
||||
|
||||
// CloseStreams closes the stdio streams for the exec
|
||||
func (c *Config) CloseStreams() error {
|
||||
return c.StreamConfig.CloseStreams()
|
||||
}
|
||||
|
||||
// SetExitCode sets the exec config's exit code
|
||||
func (c *Config) SetExitCode(code int) {
|
||||
c.ExitCode = &code
|
||||
}
|
||||
|
||||
// Store keeps track of the exec configurations.
|
||||
type Store struct {
|
||||
byID map[string]*Config
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewStore initializes a new exec store.
|
||||
func NewStore() *Store {
|
||||
return &Store{
|
||||
byID: make(map[string]*Config),
|
||||
}
|
||||
}
|
||||
|
||||
// Commands returns the exec configurations in the store.
|
||||
func (e *Store) Commands() map[string]*Config {
|
||||
e.RLock()
|
||||
byID := make(map[string]*Config, len(e.byID))
|
||||
for id, config := range e.byID {
|
||||
byID[id] = config
|
||||
}
|
||||
e.RUnlock()
|
||||
return byID
|
||||
}
|
||||
|
||||
// Add adds a new exec configuration to the store.
|
||||
func (e *Store) Add(id string, Config *Config) {
|
||||
e.Lock()
|
||||
e.byID[id] = Config
|
||||
e.Unlock()
|
||||
}
|
||||
|
||||
// Get returns an exec configuration by its id.
|
||||
func (e *Store) Get(id string) *Config {
|
||||
e.RLock()
|
||||
res := e.byID[id]
|
||||
e.RUnlock()
|
||||
return res
|
||||
}
|
||||
|
||||
// Delete removes an exec configuration from the store.
|
||||
func (e *Store) Delete(id string, pid int) {
|
||||
e.Lock()
|
||||
delete(e.byID, id)
|
||||
e.Unlock()
|
||||
}
|
||||
|
||||
// List returns the list of exec ids in the store.
|
||||
func (e *Store) List() []string {
|
||||
var IDs []string
|
||||
e.RLock()
|
||||
for id := range e.byID {
|
||||
IDs = append(IDs, id)
|
||||
}
|
||||
e.RUnlock()
|
||||
return IDs
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
import "sync"
|
||||
|
||||
type minfo struct {
|
||||
check bool
|
||||
count int
|
||||
}
|
||||
|
||||
// RefCounter is a generic counter for use by graphdriver Get/Put calls
|
||||
type RefCounter struct {
|
||||
counts map[string]*minfo
|
||||
mu sync.Mutex
|
||||
checker Checker
|
||||
}
|
||||
|
||||
// NewRefCounter returns a new RefCounter
|
||||
func NewRefCounter(c Checker) *RefCounter {
|
||||
return &RefCounter{
|
||||
checker: c,
|
||||
counts: make(map[string]*minfo),
|
||||
}
|
||||
}
|
||||
|
||||
// Increment increases the ref count for the given id and returns the current count
|
||||
func (c *RefCounter) Increment(path string) int {
|
||||
return c.incdec(path, func(minfo *minfo) {
|
||||
minfo.count++
|
||||
})
|
||||
}
|
||||
|
||||
// Decrement decreases the ref count for the given id and returns the current count
|
||||
func (c *RefCounter) Decrement(path string) int {
|
||||
return c.incdec(path, func(minfo *minfo) {
|
||||
minfo.count--
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RefCounter) incdec(path string, infoOp func(minfo *minfo)) int {
|
||||
c.mu.Lock()
|
||||
m := c.counts[path]
|
||||
if m == nil {
|
||||
m = &minfo{}
|
||||
c.counts[path] = m
|
||||
}
|
||||
// if we are checking this path for the first time check to make sure
|
||||
// if it was already mounted on the system and make sure we have a correct ref
|
||||
// count if it is mounted as it is in use.
|
||||
if !m.check {
|
||||
m.check = true
|
||||
if c.checker.IsMounted(path) {
|
||||
m.count++
|
||||
}
|
||||
}
|
||||
infoOp(m)
|
||||
count := m.count
|
||||
if count <= 0 {
|
||||
delete(c.counts, path)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return count
|
||||
}
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
)
|
||||
|
||||
// FsMagic unsigned id of the filesystem in use.
|
||||
type FsMagic uint32
|
||||
|
||||
const (
|
||||
// FsMagicUnsupported is a predefined constant value other than a valid filesystem id.
|
||||
FsMagicUnsupported = FsMagic(0x00000000)
|
||||
)
|
||||
|
||||
var (
|
||||
// All registered drivers
|
||||
drivers map[string]InitFunc
|
||||
)
|
||||
|
||||
//CreateOpts contains optional arguments for Create() and CreateReadWrite()
|
||||
// methods.
|
||||
type CreateOpts struct {
|
||||
MountLabel string
|
||||
StorageOpt map[string]string
|
||||
}
|
||||
|
||||
// InitFunc initializes the storage driver.
|
||||
type InitFunc func(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error)
|
||||
|
||||
// ProtoDriver defines the basic capabilities of a driver.
|
||||
// This interface exists solely to be a minimum set of methods
|
||||
// for client code which choose not to implement the entire Driver
|
||||
// interface and use the NaiveDiffDriver wrapper constructor.
|
||||
//
|
||||
// Use of ProtoDriver directly by client code is not recommended.
|
||||
type ProtoDriver interface {
|
||||
// String returns a string representation of this driver.
|
||||
String() string
|
||||
// CreateReadWrite creates a new, empty filesystem layer that is ready
|
||||
// to be used as the storage for a container. Additional options can
|
||||
// be passed in opts. parent may be "" and opts may be nil.
|
||||
CreateReadWrite(id, parent string, opts *CreateOpts) error
|
||||
// Create creates a new, empty, filesystem layer with the
|
||||
// specified id and parent and options passed in opts. Parent
|
||||
// may be "" and opts may be nil.
|
||||
Create(id, parent string, opts *CreateOpts) error
|
||||
// Remove attempts to remove the filesystem layer with this id.
|
||||
Remove(id string) error
|
||||
// Get returns the mountpoint for the layered filesystem referred
|
||||
// to by this id. You can optionally specify a mountLabel or "".
|
||||
// Returns the absolute path to the mounted layered filesystem.
|
||||
Get(id, mountLabel string) (fs containerfs.ContainerFS, err error)
|
||||
// Put releases the system resources for the specified id,
|
||||
// e.g, unmounting layered filesystem.
|
||||
Put(id string) error
|
||||
// Exists returns whether a filesystem layer with the specified
|
||||
// ID exists on this driver.
|
||||
Exists(id string) bool
|
||||
// Status returns a set of key-value pairs which give low
|
||||
// level diagnostic status about this driver.
|
||||
Status() [][2]string
|
||||
// Returns a set of key-value pairs which give low level information
|
||||
// about the image/container driver is managing.
|
||||
GetMetadata(id string) (map[string]string, error)
|
||||
// Cleanup performs necessary tasks to release resources
|
||||
// held by the driver, e.g., unmounting all layered filesystems
|
||||
// known to this driver.
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// DiffDriver is the interface to use to implement graph diffs
|
||||
type DiffDriver interface {
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
Diff(id, parent string) (io.ReadCloser, error)
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
Changes(id, parent string) ([]archive.Change, error)
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
// The archive.Reader must be an uncompressed stream.
|
||||
ApplyDiff(id, parent string, diff io.Reader) (size int64, err error)
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
DiffSize(id, parent string) (size int64, err error)
|
||||
}
|
||||
|
||||
// Driver is the interface for layered/snapshot file system drivers.
|
||||
type Driver interface {
|
||||
ProtoDriver
|
||||
DiffDriver
|
||||
}
|
||||
|
||||
// Capabilities defines a list of capabilities a driver may implement.
|
||||
// These capabilities are not required; however, they do determine how a
|
||||
// graphdriver can be used.
|
||||
type Capabilities struct {
|
||||
// Flags that this driver is capable of reproducing exactly equivalent
|
||||
// diffs for read-only layers. If set, clients can rely on the driver
|
||||
// for consistent tar streams, and avoid extra processing to account
|
||||
// for potential differences (eg: the layer store's use of tar-split).
|
||||
ReproducesExactDiffs bool
|
||||
}
|
||||
|
||||
// CapabilityDriver is the interface for layered file system drivers that
|
||||
// can report on their Capabilities.
|
||||
type CapabilityDriver interface {
|
||||
Capabilities() Capabilities
|
||||
}
|
||||
|
||||
// DiffGetterDriver is the interface for layered file system drivers that
|
||||
// provide a specialized function for getting file contents for tar-split.
|
||||
type DiffGetterDriver interface {
|
||||
Driver
|
||||
// DiffGetter returns an interface to efficiently retrieve the contents
|
||||
// of files in a layer.
|
||||
DiffGetter(id string) (FileGetCloser, error)
|
||||
}
|
||||
|
||||
// FileGetCloser extends the storage.FileGetter interface with a Close method
|
||||
// for cleaning up.
|
||||
type FileGetCloser interface {
|
||||
storage.FileGetter
|
||||
// Close cleans up any resources associated with the FileGetCloser.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Checker makes checks on specified filesystems.
|
||||
type Checker interface {
|
||||
// IsMounted returns true if the provided path is mounted for the specific checker
|
||||
IsMounted(path string) bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = make(map[string]InitFunc)
|
||||
}
|
||||
|
||||
// Register registers an InitFunc for the driver.
|
||||
func Register(name string, initFunc InitFunc) error {
|
||||
if _, exists := drivers[name]; exists {
|
||||
return fmt.Errorf("Name already registered %s", name)
|
||||
}
|
||||
drivers[name] = initFunc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDriver initializes and returns the registered driver
|
||||
func GetDriver(name string, pg plugingetter.PluginGetter, config Options) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(config.Root, name), config.DriverOptions, config.UIDMaps, config.GIDMaps)
|
||||
}
|
||||
|
||||
pluginDriver, err := lookupPlugin(name, pg, config)
|
||||
if err == nil {
|
||||
return pluginDriver, nil
|
||||
}
|
||||
logrus.WithError(err).WithField("driver", name).WithField("home-dir", config.Root).Error("Failed to GetDriver graph")
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// getBuiltinDriver initializes and returns the registered driver, but does not try to load from plugins
|
||||
func getBuiltinDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
|
||||
}
|
||||
logrus.Errorf("Failed to built-in GetDriver graph %s %s", name, home)
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// Options is used to initialize a graphdriver
|
||||
type Options struct {
|
||||
Root string
|
||||
DriverOptions []string
|
||||
UIDMaps []idtools.IDMap
|
||||
GIDMaps []idtools.IDMap
|
||||
ExperimentalEnabled bool
|
||||
}
|
||||
|
||||
// New creates the driver and initializes it at the specified root.
|
||||
func New(name string, pg plugingetter.PluginGetter, config Options) (Driver, error) {
|
||||
if name != "" {
|
||||
logrus.Debugf("[graphdriver] trying provided driver: %s", name) // so the logs show specified driver
|
||||
return GetDriver(name, pg, config)
|
||||
}
|
||||
|
||||
// Guess for prior driver
|
||||
driversMap := scanPriorDrivers(config.Root)
|
||||
list := strings.Split(priority, ",")
|
||||
logrus.Debugf("[graphdriver] priority list: %v", list)
|
||||
for _, name := range list {
|
||||
if name == "vfs" {
|
||||
// don't use vfs even if there is state present.
|
||||
continue
|
||||
}
|
||||
if _, prior := driversMap[name]; prior {
|
||||
// of the state found from prior drivers, check in order of our priority
|
||||
// which we would prefer
|
||||
driver, err := getBuiltinDriver(name, config.Root, config.DriverOptions, config.UIDMaps, config.GIDMaps)
|
||||
if err != nil {
|
||||
// unlike below, we will return error here, because there is prior
|
||||
// state, and now it is no longer supported/prereq/compatible, so
|
||||
// something changed and needs attention. Otherwise the daemon's
|
||||
// images would just "disappear".
|
||||
logrus.Errorf("[graphdriver] prior storage driver %s failed: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// abort starting when there are other prior configured drivers
|
||||
// to ensure the user explicitly selects the driver to load
|
||||
if len(driversMap)-1 > 0 {
|
||||
var driversSlice []string
|
||||
for name := range driversMap {
|
||||
driversSlice = append(driversSlice, name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s contains several valid graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s <DRIVER>)", config.Root, strings.Join(driversSlice, ", "))
|
||||
}
|
||||
|
||||
logrus.Infof("[graphdriver] using prior storage driver: %s", name)
|
||||
return driver, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check for priority drivers first
|
||||
for _, name := range list {
|
||||
driver, err := getBuiltinDriver(name, config.Root, config.DriverOptions, config.UIDMaps, config.GIDMaps)
|
||||
if err != nil {
|
||||
if IsDriverNotSupported(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// Check all registered drivers if no priority driver is found
|
||||
for name, initFunc := range drivers {
|
||||
driver, err := initFunc(filepath.Join(config.Root, name), config.DriverOptions, config.UIDMaps, config.GIDMaps)
|
||||
if err != nil {
|
||||
if IsDriverNotSupported(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
return nil, fmt.Errorf("No supported storage backend found")
|
||||
}
|
||||
|
||||
// scanPriorDrivers returns an un-ordered scan of directories of prior storage drivers
|
||||
func scanPriorDrivers(root string) map[string]bool {
|
||||
driversMap := make(map[string]bool)
|
||||
|
||||
for driver := range drivers {
|
||||
p := filepath.Join(root, driver)
|
||||
if _, err := os.Stat(p); err == nil && driver != "vfs" {
|
||||
if !isEmptyDir(p) {
|
||||
driversMap[driver] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return driversMap
|
||||
}
|
||||
|
||||
// IsInitialized checks if the driver's home-directory exists and is non-empty.
|
||||
func IsInitialized(driverHome string) bool {
|
||||
_, err := os.Stat(driverHome)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Warnf("graphdriver.IsInitialized: stat failed: %v", err)
|
||||
}
|
||||
return !isEmptyDir(driverHome)
|
||||
}
|
||||
|
||||
// isEmptyDir checks if a directory is empty. It is used to check if prior
|
||||
// storage-driver directories exist. If an error occurs, it also assumes the
|
||||
// directory is not empty (which preserves the behavior _before_ this check
|
||||
// was added)
|
||||
func isEmptyDir(name string) bool {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err = f.Readdirnames(1); err == io.EOF {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
// List of drivers that should be used in an order
|
||||
priority = "zfs"
|
||||
)
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
var buf unix.Statfs_t
|
||||
if err := syscall.Statfs(mountPath, &buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return FsMagic(buf.Type) == fsType, nil
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicAufs filesystem id for Aufs
|
||||
FsMagicAufs = FsMagic(0x61756673)
|
||||
// FsMagicBtrfs filesystem id for Btrfs
|
||||
FsMagicBtrfs = FsMagic(0x9123683E)
|
||||
// FsMagicCramfs filesystem id for Cramfs
|
||||
FsMagicCramfs = FsMagic(0x28cd3d45)
|
||||
// FsMagicEcryptfs filesystem id for eCryptfs
|
||||
FsMagicEcryptfs = FsMagic(0xf15f)
|
||||
// FsMagicExtfs filesystem id for Extfs
|
||||
FsMagicExtfs = FsMagic(0x0000EF53)
|
||||
// FsMagicF2fs filesystem id for F2fs
|
||||
FsMagicF2fs = FsMagic(0xF2F52010)
|
||||
// FsMagicGPFS filesystem id for GPFS
|
||||
FsMagicGPFS = FsMagic(0x47504653)
|
||||
// FsMagicJffs2Fs filesystem if for Jffs2Fs
|
||||
FsMagicJffs2Fs = FsMagic(0x000072b6)
|
||||
// FsMagicJfs filesystem id for Jfs
|
||||
FsMagicJfs = FsMagic(0x3153464a)
|
||||
// FsMagicNfsFs filesystem id for NfsFs
|
||||
FsMagicNfsFs = FsMagic(0x00006969)
|
||||
// FsMagicRAMFs filesystem id for RamFs
|
||||
FsMagicRAMFs = FsMagic(0x858458f6)
|
||||
// FsMagicReiserFs filesystem id for ReiserFs
|
||||
FsMagicReiserFs = FsMagic(0x52654973)
|
||||
// FsMagicSmbFs filesystem id for SmbFs
|
||||
FsMagicSmbFs = FsMagic(0x0000517B)
|
||||
// FsMagicSquashFs filesystem id for SquashFs
|
||||
FsMagicSquashFs = FsMagic(0x73717368)
|
||||
// FsMagicTmpFs filesystem id for TmpFs
|
||||
FsMagicTmpFs = FsMagic(0x01021994)
|
||||
// FsMagicVxFS filesystem id for VxFs
|
||||
FsMagicVxFS = FsMagic(0xa501fcf5)
|
||||
// FsMagicXfs filesystem id for Xfs
|
||||
FsMagicXfs = FsMagic(0x58465342)
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
// FsMagicOverlay filesystem id for overlay
|
||||
FsMagicOverlay = FsMagic(0x794C7630)
|
||||
)
|
||||
|
||||
var (
|
||||
// List of drivers that should be used in an order
|
||||
priority = "btrfs,zfs,overlay2,aufs,overlay,devicemapper,vfs"
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicAufs: "aufs",
|
||||
FsMagicBtrfs: "btrfs",
|
||||
FsMagicCramfs: "cramfs",
|
||||
FsMagicEcryptfs: "ecryptfs",
|
||||
FsMagicExtfs: "extfs",
|
||||
FsMagicF2fs: "f2fs",
|
||||
FsMagicGPFS: "gpfs",
|
||||
FsMagicJffs2Fs: "jffs2",
|
||||
FsMagicJfs: "jfs",
|
||||
FsMagicNfsFs: "nfs",
|
||||
FsMagicOverlay: "overlayfs",
|
||||
FsMagicRAMFs: "ramfs",
|
||||
FsMagicReiserFs: "reiserfs",
|
||||
FsMagicSmbFs: "smb",
|
||||
FsMagicSquashFs: "squashfs",
|
||||
FsMagicTmpFs: "tmpfs",
|
||||
FsMagicUnsupported: "unsupported",
|
||||
FsMagicVxFS: "vxfs",
|
||||
FsMagicXfs: "xfs",
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
var buf unix.Statfs_t
|
||||
if err := unix.Statfs(rootpath, &buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return FsMagic(buf.Type), nil
|
||||
}
|
||||
|
||||
// NewFsChecker returns a checker configured for the provided FsMagic
|
||||
func NewFsChecker(t FsMagic) Checker {
|
||||
return &fsChecker{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
type fsChecker struct {
|
||||
t FsMagic
|
||||
}
|
||||
|
||||
func (c *fsChecker) IsMounted(path string) bool {
|
||||
m, _ := Mounted(c.t, path)
|
||||
return m
|
||||
}
|
||||
|
||||
// NewDefaultChecker returns a check that parses /proc/mountinfo to check
|
||||
// if the specified path is mounted.
|
||||
func NewDefaultChecker() Checker {
|
||||
return &defaultChecker{}
|
||||
}
|
||||
|
||||
type defaultChecker struct {
|
||||
}
|
||||
|
||||
func (c *defaultChecker) IsMounted(path string) bool {
|
||||
m, _ := mount.Mounted(path)
|
||||
return m
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
var buf unix.Statfs_t
|
||||
if err := unix.Statfs(mountPath, &buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return FsMagic(buf.Type) == fsType, nil
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// +build !linux,!windows,!freebsd
|
||||
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
var (
|
||||
// List of drivers that should be used in an order
|
||||
priority = "unsupported"
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
var (
|
||||
// List of drivers that should be used in order
|
||||
priority = "windowsfilter"
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
// Note it is OK to return FsMagicUnsupported on Windows.
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
const (
|
||||
// ErrNotSupported returned when driver is not supported.
|
||||
ErrNotSupported NotSupportedError = "driver not supported"
|
||||
// ErrPrerequisites returned when driver does not meet prerequisites.
|
||||
ErrPrerequisites NotSupportedError = "prerequisites for driver not satisfied (wrong filesystem?)"
|
||||
// ErrIncompatibleFS returned when file system is not supported.
|
||||
ErrIncompatibleFS NotSupportedError = "backing file system is unsupported for this graph driver"
|
||||
)
|
||||
|
||||
// ErrUnSupported signals that the graph-driver is not supported on the current configuration
|
||||
type ErrUnSupported interface {
|
||||
NotSupported()
|
||||
}
|
||||
|
||||
// NotSupportedError signals that the graph-driver is not supported on the current configuration
|
||||
type NotSupportedError string
|
||||
|
||||
func (e NotSupportedError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// NotSupported signals that a graph-driver is not supported.
|
||||
func (e NotSupportedError) NotSupported() {}
|
||||
|
||||
// IsDriverNotSupported returns true if the error initializing
|
||||
// the graph driver is a non-supported error.
|
||||
func IsDriverNotSupported(err error) bool {
|
||||
switch err.(type) {
|
||||
case ErrUnSupported:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// ApplyUncompressedLayer defines the unpack method used by the graph
|
||||
// driver.
|
||||
ApplyUncompressedLayer = chrootarchive.ApplyUncompressedLayer
|
||||
)
|
||||
|
||||
// NaiveDiffDriver takes a ProtoDriver and adds the
|
||||
// capability of the Diffing methods on the local file system,
|
||||
// which it may or may not support on its own. See the comment
|
||||
// on the exported NewNaiveDiffDriver function below.
|
||||
// Notably, the AUFS driver doesn't need to be wrapped like this.
|
||||
type NaiveDiffDriver struct {
|
||||
ProtoDriver
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
}
|
||||
|
||||
// NewNaiveDiffDriver returns a fully functional driver that wraps the
|
||||
// given ProtoDriver and adds the capability of the following methods which
|
||||
// it may or may not support on its own:
|
||||
// Diff(id, parent string) (archive.Archive, error)
|
||||
// Changes(id, parent string) ([]archive.Change, error)
|
||||
// ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error)
|
||||
// DiffSize(id, parent string) (size int64, err error)
|
||||
func NewNaiveDiffDriver(driver ProtoDriver, uidMaps, gidMaps []idtools.IDMap) Driver {
|
||||
return &NaiveDiffDriver{ProtoDriver: driver,
|
||||
uidMaps: uidMaps,
|
||||
gidMaps: gidMaps}
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (gdw *NaiveDiffDriver) Diff(id, parent string) (arch io.ReadCloser, err error) {
|
||||
startTime := time.Now()
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
layerRootFs, err := driver.Get(id, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layerFs := layerRootFs.Path()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
driver.Put(id)
|
||||
}
|
||||
}()
|
||||
|
||||
if parent == "" {
|
||||
archive, err := archive.Tar(layerFs, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
driver.Put(id)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
parentRootFs, err := driver.Get(parent, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driver.Put(parent)
|
||||
|
||||
parentFs := parentRootFs.Path()
|
||||
|
||||
changes, err := archive.ChangesDirs(layerFs, parentFs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archive, err := archive.ExportChanges(layerFs, changes, gdw.uidMaps, gdw.gidMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
driver.Put(id)
|
||||
|
||||
// NaiveDiffDriver compares file metadata with parent layers. Parent layers
|
||||
// are extracted from tar's with full second precision on modified time.
|
||||
// We need this hack here to make sure calls within same second receive
|
||||
// correct result.
|
||||
time.Sleep(time.Until(startTime.Truncate(time.Second).Add(time.Second)))
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (gdw *NaiveDiffDriver) Changes(id, parent string) ([]archive.Change, error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
layerRootFs, err := driver.Get(id, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driver.Put(id)
|
||||
|
||||
layerFs := layerRootFs.Path()
|
||||
parentFs := ""
|
||||
|
||||
if parent != "" {
|
||||
parentRootFs, err := driver.Get(parent, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driver.Put(parent)
|
||||
parentFs = parentRootFs.Path()
|
||||
}
|
||||
|
||||
return archive.ChangesDirs(layerFs, parentFs)
|
||||
}
|
||||
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, diff io.Reader) (size int64, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
// Mount the root filesystem so we can apply the diff/layer.
|
||||
layerRootFs, err := driver.Get(id, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer driver.Put(id)
|
||||
|
||||
layerFs := layerRootFs.Path()
|
||||
options := &archive.TarOptions{UIDMaps: gdw.uidMaps,
|
||||
GIDMaps: gdw.gidMaps}
|
||||
start := time.Now().UTC()
|
||||
logrus.Debug("Start untar layer")
|
||||
if size, err = ApplyUncompressedLayer(layerFs, diff, options); err != nil {
|
||||
return
|
||||
}
|
||||
logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified layer
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
func (gdw *NaiveDiffDriver) DiffSize(id, parent string) (size int64, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
changes, err := gdw.Changes(id, parent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
layerFs, err := driver.Get(id, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer driver.Put(id)
|
||||
|
||||
return archive.ChangesSize(layerFs.Path(), changes), nil
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/docker/docker/plugin/v2"
|
||||
)
|
||||
|
||||
func lookupPlugin(name string, pg plugingetter.PluginGetter, config Options) (Driver, error) {
|
||||
if !config.ExperimentalEnabled {
|
||||
return nil, fmt.Errorf("graphdriver plugins are only supported with experimental mode")
|
||||
}
|
||||
pl, err := pg.Get(name, "GraphDriver", plugingetter.Acquire)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error looking up graphdriver plugin %s: %v", name, err)
|
||||
}
|
||||
return newPluginDriver(name, pl, config)
|
||||
}
|
||||
|
||||
func newPluginDriver(name string, pl plugingetter.CompatPlugin, config Options) (Driver, error) {
|
||||
home := config.Root
|
||||
if !pl.IsV1() {
|
||||
if p, ok := pl.(*v2.Plugin); ok {
|
||||
if p.PluginObj.Config.PropagatedMount != "" {
|
||||
home = p.PluginObj.Config.PropagatedMount
|
||||
}
|
||||
}
|
||||
}
|
||||
proxy := &graphDriverProxy{name, pl, Capabilities{}}
|
||||
return proxy, proxy.Init(filepath.Join(home, name), config.DriverOptions, config.UIDMaps, config.GIDMaps)
|
||||
}
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/containerfs"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
)
|
||||
|
||||
type graphDriverProxy struct {
|
||||
name string
|
||||
p plugingetter.CompatPlugin
|
||||
caps Capabilities
|
||||
}
|
||||
|
||||
type graphDriverRequest struct {
|
||||
ID string `json:",omitempty"`
|
||||
Parent string `json:",omitempty"`
|
||||
MountLabel string `json:",omitempty"`
|
||||
StorageOpt map[string]string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type graphDriverResponse struct {
|
||||
Err string `json:",omitempty"`
|
||||
Dir string `json:",omitempty"`
|
||||
Exists bool `json:",omitempty"`
|
||||
Status [][2]string `json:",omitempty"`
|
||||
Changes []archive.Change `json:",omitempty"`
|
||||
Size int64 `json:",omitempty"`
|
||||
Metadata map[string]string `json:",omitempty"`
|
||||
Capabilities Capabilities `json:",omitempty"`
|
||||
}
|
||||
|
||||
type graphDriverInitRequest struct {
|
||||
Home string
|
||||
Opts []string `json:"Opts"`
|
||||
UIDMaps []idtools.IDMap `json:"UIDMaps"`
|
||||
GIDMaps []idtools.IDMap `json:"GIDMaps"`
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Init(home string, opts []string, uidMaps, gidMaps []idtools.IDMap) error {
|
||||
if !d.p.IsV1() {
|
||||
if cp, ok := d.p.(plugingetter.CountedPlugin); ok {
|
||||
// always acquire here, it will be cleaned up on daemon shutdown
|
||||
cp.Acquire()
|
||||
}
|
||||
}
|
||||
args := &graphDriverInitRequest{
|
||||
Home: home,
|
||||
Opts: opts,
|
||||
UIDMaps: uidMaps,
|
||||
GIDMaps: gidMaps,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Init", args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
caps, err := d.fetchCaps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.caps = caps
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) fetchCaps() (Capabilities, error) {
|
||||
args := &graphDriverRequest{}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Capabilities", args, &ret); err != nil {
|
||||
if !plugins.IsNotFound(err) {
|
||||
return Capabilities{}, err
|
||||
}
|
||||
}
|
||||
return ret.Capabilities, nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) String() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Capabilities() Capabilities {
|
||||
return d.caps
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) CreateReadWrite(id, parent string, opts *CreateOpts) error {
|
||||
return d.create("GraphDriver.CreateReadWrite", id, parent, opts)
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Create(id, parent string, opts *CreateOpts) error {
|
||||
return d.create("GraphDriver.Create", id, parent, opts)
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) create(method, id, parent string, opts *CreateOpts) error {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
}
|
||||
if opts != nil {
|
||||
args.MountLabel = opts.MountLabel
|
||||
args.StorageOpt = opts.StorageOpt
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call(method, args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Remove(id string) error {
|
||||
args := &graphDriverRequest{ID: id}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Remove", args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Get", args, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
return containerfs.NewLocalContainerFS(d.p.ScopedPath(ret.Dir)), err
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Put(id string) error {
|
||||
args := &graphDriverRequest{ID: id}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Put", args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Exists(id string) bool {
|
||||
args := &graphDriverRequest{ID: id}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Exists", args, &ret); err != nil {
|
||||
return false
|
||||
}
|
||||
return ret.Exists
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Status() [][2]string {
|
||||
args := &graphDriverRequest{}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Status", args, &ret); err != nil {
|
||||
return nil
|
||||
}
|
||||
return ret.Status
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) GetMetadata(id string) (map[string]string, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.GetMetadata", args, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return nil, errors.New(ret.Err)
|
||||
}
|
||||
return ret.Metadata, nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Cleanup() error {
|
||||
if !d.p.IsV1() {
|
||||
if cp, ok := d.p.(plugingetter.CountedPlugin); ok {
|
||||
// always release
|
||||
defer cp.Release()
|
||||
}
|
||||
}
|
||||
|
||||
args := &graphDriverRequest{}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Cleanup", args, &ret); err != nil {
|
||||
return nil
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Diff(id, parent string) (io.ReadCloser, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
}
|
||||
body, err := d.p.Client().Stream("GraphDriver.Diff", args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Changes(id, parent string) ([]archive.Change, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.Changes", args, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return nil, errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return ret.Changes, nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().SendFile(fmt.Sprintf("GraphDriver.ApplyDiff?id=%s&parent=%s", id, parent), diff, &ret); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return -1, errors.New(ret.Err)
|
||||
}
|
||||
return ret.Size, nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) DiffSize(id, parent string) (int64, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.p.Client().Call("GraphDriver.DiffSize", args, &ret); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return -1, errors.New(ret.Err)
|
||||
}
|
||||
return ret.Size, nil
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/plugins/logdriver"
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// pluginAdapter takes a plugin and implements the Logger interface for logger
|
||||
// instances
|
||||
type pluginAdapter struct {
|
||||
driverName string
|
||||
id string
|
||||
plugin logPlugin
|
||||
fifoPath string
|
||||
capabilities Capability
|
||||
logInfo Info
|
||||
|
||||
// synchronize access to the log stream and shared buffer
|
||||
mu sync.Mutex
|
||||
enc logdriver.LogEntryEncoder
|
||||
stream io.WriteCloser
|
||||
// buf is shared for each `Log()` call to reduce allocations.
|
||||
// buf must be protected by mutex
|
||||
buf logdriver.LogEntry
|
||||
}
|
||||
|
||||
func (a *pluginAdapter) Log(msg *Message) error {
|
||||
a.mu.Lock()
|
||||
|
||||
a.buf.Line = msg.Line
|
||||
a.buf.TimeNano = msg.Timestamp.UnixNano()
|
||||
a.buf.Partial = (msg.PLogMetaData != nil)
|
||||
a.buf.Source = msg.Source
|
||||
|
||||
err := a.enc.Encode(&a.buf)
|
||||
a.buf.Reset()
|
||||
|
||||
a.mu.Unlock()
|
||||
|
||||
PutMessage(msg)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *pluginAdapter) Name() string {
|
||||
return a.driverName
|
||||
}
|
||||
|
||||
func (a *pluginAdapter) Close() error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if err := a.plugin.StopLogging(filepath.Join("/", "run", "docker", "logging", a.id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.stream.Close(); err != nil {
|
||||
logrus.WithError(err).Error("error closing plugin fifo")
|
||||
}
|
||||
if err := os.Remove(a.fifoPath); err != nil && !os.IsNotExist(err) {
|
||||
logrus.WithError(err).Error("error cleaning up plugin fifo")
|
||||
}
|
||||
|
||||
// may be nil, especially for unit tests
|
||||
if pluginGetter != nil {
|
||||
pluginGetter.Get(a.Name(), extName, plugingetter.Release)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type pluginAdapterWithRead struct {
|
||||
*pluginAdapter
|
||||
}
|
||||
|
||||
func (a *pluginAdapterWithRead) ReadLogs(config ReadConfig) *LogWatcher {
|
||||
watcher := NewLogWatcher()
|
||||
|
||||
go func() {
|
||||
defer close(watcher.Msg)
|
||||
stream, err := a.plugin.ReadLogs(a.logInfo, config)
|
||||
if err != nil {
|
||||
watcher.Err <- errors.Wrap(err, "error getting log reader")
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
dec := logdriver.NewLogEntryDecoder(stream)
|
||||
for {
|
||||
select {
|
||||
case <-watcher.WatchClose():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
var buf logdriver.LogEntry
|
||||
if err := dec.Decode(&buf); err != nil {
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case watcher.Err <- errors.Wrap(err, "error decoding log message"):
|
||||
case <-watcher.WatchClose():
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
Timestamp: time.Unix(0, buf.TimeNano),
|
||||
Line: buf.Line,
|
||||
Source: buf.Source,
|
||||
}
|
||||
|
||||
// plugin should handle this, but check just in case
|
||||
if !config.Since.IsZero() && msg.Timestamp.Before(config.Since) {
|
||||
continue
|
||||
}
|
||||
if !config.Until.IsZero() && msg.Timestamp.After(config.Until) {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case watcher.Msg <- msg:
|
||||
case <-watcher.WatchClose():
|
||||
// make sure the message we consumed is sent
|
||||
watcher.Msg <- msg
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return watcher
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
types "github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// readSize is the maximum bytes read during a single read
|
||||
// operation.
|
||||
readSize = 2 * 1024
|
||||
|
||||
// defaultBufSize provides a reasonable default for loggers that do
|
||||
// not have an external limit to impose on log line size.
|
||||
defaultBufSize = 16 * 1024
|
||||
)
|
||||
|
||||
// Copier can copy logs from specified sources to Logger and attach Timestamp.
|
||||
// Writes are concurrent, so you need implement some sync in your logger.
|
||||
type Copier struct {
|
||||
// srcs is map of name -> reader pairs, for example "stdout", "stderr"
|
||||
srcs map[string]io.Reader
|
||||
dst Logger
|
||||
copyJobs sync.WaitGroup
|
||||
closeOnce sync.Once
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
// NewCopier creates a new Copier
|
||||
func NewCopier(srcs map[string]io.Reader, dst Logger) *Copier {
|
||||
return &Copier{
|
||||
srcs: srcs,
|
||||
dst: dst,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts logs copying
|
||||
func (c *Copier) Run() {
|
||||
for src, w := range c.srcs {
|
||||
c.copyJobs.Add(1)
|
||||
go c.copySrc(src, w)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Copier) copySrc(name string, src io.Reader) {
|
||||
defer c.copyJobs.Done()
|
||||
|
||||
bufSize := defaultBufSize
|
||||
if sizedLogger, ok := c.dst.(SizedLogger); ok {
|
||||
bufSize = sizedLogger.BufSize()
|
||||
}
|
||||
buf := make([]byte, bufSize)
|
||||
|
||||
n := 0
|
||||
eof := false
|
||||
var partialid string
|
||||
var partialTS time.Time
|
||||
var ordinal int
|
||||
firstPartial := true
|
||||
hasMorePartial := false
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.closed:
|
||||
return
|
||||
default:
|
||||
// Work out how much more data we are okay with reading this time.
|
||||
upto := n + readSize
|
||||
if upto > cap(buf) {
|
||||
upto = cap(buf)
|
||||
}
|
||||
// Try to read that data.
|
||||
if upto > n {
|
||||
read, err := src.Read(buf[n:upto])
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
logrus.Errorf("Error scanning log stream: %s", err)
|
||||
return
|
||||
}
|
||||
eof = true
|
||||
}
|
||||
n += read
|
||||
}
|
||||
// If we have no data to log, and there's no more coming, we're done.
|
||||
if n == 0 && eof {
|
||||
return
|
||||
}
|
||||
// Break up the data that we've buffered up into lines, and log each in turn.
|
||||
p := 0
|
||||
|
||||
for q := bytes.IndexByte(buf[p:n], '\n'); q >= 0; q = bytes.IndexByte(buf[p:n], '\n') {
|
||||
select {
|
||||
case <-c.closed:
|
||||
return
|
||||
default:
|
||||
msg := NewMessage()
|
||||
msg.Source = name
|
||||
msg.Line = append(msg.Line, buf[p:p+q]...)
|
||||
|
||||
if hasMorePartial {
|
||||
msg.PLogMetaData = &types.PartialLogMetaData{ID: partialid, Ordinal: ordinal, Last: true}
|
||||
|
||||
// reset
|
||||
partialid = ""
|
||||
ordinal = 0
|
||||
firstPartial = true
|
||||
hasMorePartial = false
|
||||
}
|
||||
if msg.PLogMetaData == nil {
|
||||
msg.Timestamp = time.Now().UTC()
|
||||
} else {
|
||||
msg.Timestamp = partialTS
|
||||
}
|
||||
|
||||
if logErr := c.dst.Log(msg); logErr != nil {
|
||||
logrus.Errorf("Failed to log msg %q for logger %s: %s", msg.Line, c.dst.Name(), logErr)
|
||||
}
|
||||
}
|
||||
p += q + 1
|
||||
}
|
||||
// If there's no more coming, or the buffer is full but
|
||||
// has no newlines, log whatever we haven't logged yet,
|
||||
// noting that it's a partial log line.
|
||||
if eof || (p == 0 && n == len(buf)) {
|
||||
if p < n {
|
||||
msg := NewMessage()
|
||||
msg.Source = name
|
||||
msg.Line = append(msg.Line, buf[p:n]...)
|
||||
|
||||
// Generate unique partialID for first partial. Use it across partials.
|
||||
// Record timestamp for first partial. Use it across partials.
|
||||
// Initialize Ordinal for first partial. Increment it across partials.
|
||||
if firstPartial {
|
||||
msg.Timestamp = time.Now().UTC()
|
||||
partialTS = msg.Timestamp
|
||||
partialid = stringid.GenerateRandomID()
|
||||
ordinal = 1
|
||||
firstPartial = false
|
||||
} else {
|
||||
msg.Timestamp = partialTS
|
||||
}
|
||||
msg.PLogMetaData = &types.PartialLogMetaData{ID: partialid, Ordinal: ordinal, Last: false}
|
||||
ordinal++
|
||||
hasMorePartial = true
|
||||
|
||||
if logErr := c.dst.Log(msg); logErr != nil {
|
||||
logrus.Errorf("Failed to log msg %q for logger %s: %s", msg.Line, c.dst.Name(), logErr)
|
||||
}
|
||||
p = 0
|
||||
n = 0
|
||||
}
|
||||
if eof {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Move any unlogged data to the front of the buffer in preparation for another read.
|
||||
if p > 0 {
|
||||
copy(buf[0:], buf[p:n])
|
||||
n -= p
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait waits until all copying is done
|
||||
func (c *Copier) Wait() {
|
||||
c.copyJobs.Wait()
|
||||
}
|
||||
|
||||
// Close closes the copier
|
||||
func (c *Copier) Close() {
|
||||
c.closeOnce.Do(func() {
|
||||
close(c.closed)
|
||||
})
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Creator builds a logging driver instance with given context.
|
||||
type Creator func(Info) (Logger, error)
|
||||
|
||||
// LogOptValidator checks the options specific to the underlying
|
||||
// logging implementation.
|
||||
type LogOptValidator func(cfg map[string]string) error
|
||||
|
||||
type logdriverFactory struct {
|
||||
registry map[string]Creator
|
||||
optValidator map[string]LogOptValidator
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (lf *logdriverFactory) list() []string {
|
||||
ls := make([]string, 0, len(lf.registry))
|
||||
lf.m.Lock()
|
||||
for name := range lf.registry {
|
||||
ls = append(ls, name)
|
||||
}
|
||||
lf.m.Unlock()
|
||||
sort.Strings(ls)
|
||||
return ls
|
||||
}
|
||||
|
||||
// ListDrivers gets the list of registered log driver names
|
||||
func ListDrivers() []string {
|
||||
return factory.list()
|
||||
}
|
||||
|
||||
func (lf *logdriverFactory) register(name string, c Creator) error {
|
||||
if lf.driverRegistered(name) {
|
||||
return fmt.Errorf("logger: log driver named '%s' is already registered", name)
|
||||
}
|
||||
|
||||
lf.m.Lock()
|
||||
lf.registry[name] = c
|
||||
lf.m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lf *logdriverFactory) driverRegistered(name string) bool {
|
||||
lf.m.Lock()
|
||||
_, ok := lf.registry[name]
|
||||
lf.m.Unlock()
|
||||
if !ok {
|
||||
if pluginGetter != nil { // this can be nil when the init functions are running
|
||||
if l, _ := getPlugin(name, plugingetter.Lookup); l != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (lf *logdriverFactory) registerLogOptValidator(name string, l LogOptValidator) error {
|
||||
lf.m.Lock()
|
||||
defer lf.m.Unlock()
|
||||
|
||||
if _, ok := lf.optValidator[name]; ok {
|
||||
return fmt.Errorf("logger: log validator named '%s' is already registered", name)
|
||||
}
|
||||
lf.optValidator[name] = l
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lf *logdriverFactory) get(name string) (Creator, error) {
|
||||
lf.m.Lock()
|
||||
defer lf.m.Unlock()
|
||||
|
||||
c, ok := lf.registry[name]
|
||||
if ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
c, err := getPlugin(name, plugingetter.Acquire)
|
||||
return c, errors.Wrapf(err, "logger: no log driver named '%s' is registered", name)
|
||||
}
|
||||
|
||||
func (lf *logdriverFactory) getLogOptValidator(name string) LogOptValidator {
|
||||
lf.m.Lock()
|
||||
defer lf.m.Unlock()
|
||||
|
||||
c := lf.optValidator[name]
|
||||
return c
|
||||
}
|
||||
|
||||
var factory = &logdriverFactory{registry: make(map[string]Creator), optValidator: make(map[string]LogOptValidator)} // global factory instance
|
||||
|
||||
// RegisterLogDriver registers the given logging driver builder with given logging
|
||||
// driver name.
|
||||
func RegisterLogDriver(name string, c Creator) error {
|
||||
return factory.register(name, c)
|
||||
}
|
||||
|
||||
// RegisterLogOptValidator registers the logging option validator with
|
||||
// the given logging driver name.
|
||||
func RegisterLogOptValidator(name string, l LogOptValidator) error {
|
||||
return factory.registerLogOptValidator(name, l)
|
||||
}
|
||||
|
||||
// GetLogDriver provides the logging driver builder for a logging driver name.
|
||||
func GetLogDriver(name string) (Creator, error) {
|
||||
return factory.get(name)
|
||||
}
|
||||
|
||||
var builtInLogOpts = map[string]bool{
|
||||
"mode": true,
|
||||
"max-buffer-size": true,
|
||||
}
|
||||
|
||||
// ValidateLogOpts checks the options for the given log driver. The
|
||||
// options supported are specific to the LogDriver implementation.
|
||||
func ValidateLogOpts(name string, cfg map[string]string) error {
|
||||
if name == "none" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch containertypes.LogMode(cfg["mode"]) {
|
||||
case containertypes.LogModeBlocking, containertypes.LogModeNonBlock, containertypes.LogModeUnset:
|
||||
default:
|
||||
return fmt.Errorf("logger: logging mode not supported: %s", cfg["mode"])
|
||||
}
|
||||
|
||||
if s, ok := cfg["max-buffer-size"]; ok {
|
||||
if containertypes.LogMode(cfg["mode"]) != containertypes.LogModeNonBlock {
|
||||
return fmt.Errorf("logger: max-buffer-size option is only supported with 'mode=%s'", containertypes.LogModeNonBlock)
|
||||
}
|
||||
if _, err := units.RAMInBytes(s); err != nil {
|
||||
return errors.Wrap(err, "error parsing option max-buffer-size")
|
||||
}
|
||||
}
|
||||
|
||||
if !factory.driverRegistered(name) {
|
||||
return fmt.Errorf("logger: no log driver named '%s' is registered", name)
|
||||
}
|
||||
|
||||
filteredOpts := make(map[string]string, len(builtInLogOpts))
|
||||
for k, v := range cfg {
|
||||
if !builtInLogOpts[k] {
|
||||
filteredOpts[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
validator := factory.getLogOptValidator(name)
|
||||
if validator != nil {
|
||||
return validator(filteredOpts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
// Package jsonfilelog provides the default Logger implementation for
|
||||
// Docker logging. This logger logs to files on the host server in the
|
||||
// JSON format.
|
||||
package jsonfilelog // import "github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/daemon/logger"
|
||||
"github.com/docker/docker/daemon/logger/jsonfilelog/jsonlog"
|
||||
"github.com/docker/docker/daemon/logger/loggerutils"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Name is the name of the file that the jsonlogger logs to.
|
||||
const Name = "json-file"
|
||||
|
||||
// JSONFileLogger is Logger implementation for default Docker logging.
|
||||
type JSONFileLogger struct {
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
writer *loggerutils.LogFile
|
||||
readers map[*logger.LogWatcher]struct{} // stores the active log followers
|
||||
tag string // tag values requested by the user to log
|
||||
}
|
||||
|
||||
func init() {
|
||||
if err := logger.RegisterLogDriver(Name, New); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if err := logger.RegisterLogOptValidator(Name, ValidateLogOpt); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates new JSONFileLogger which writes to filename passed in
|
||||
// on given context.
|
||||
func New(info logger.Info) (logger.Logger, error) {
|
||||
var capval int64 = -1
|
||||
if capacity, ok := info.Config["max-size"]; ok {
|
||||
var err error
|
||||
capval, err = units.FromHumanSize(capacity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if capval <= 0 {
|
||||
return nil, fmt.Errorf("max-size should be a positive numbler")
|
||||
}
|
||||
}
|
||||
var maxFiles = 1
|
||||
if maxFileString, ok := info.Config["max-file"]; ok {
|
||||
var err error
|
||||
maxFiles, err = strconv.Atoi(maxFileString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if maxFiles < 1 {
|
||||
return nil, fmt.Errorf("max-file cannot be less than 1")
|
||||
}
|
||||
}
|
||||
|
||||
var compress bool
|
||||
if compressString, ok := info.Config["compress"]; ok {
|
||||
var err error
|
||||
compress, err = strconv.ParseBool(compressString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if compress && (maxFiles == 1 || capval == -1) {
|
||||
return nil, fmt.Errorf("compress cannot be true when max-file is less than 2 or max-size is not set")
|
||||
}
|
||||
}
|
||||
|
||||
attrs, err := info.ExtraAttributes(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// no default template. only use a tag if the user asked for it
|
||||
tag, err := loggerutils.ParseLogTag(info, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tag != "" {
|
||||
attrs["tag"] = tag
|
||||
}
|
||||
|
||||
var extra []byte
|
||||
if len(attrs) > 0 {
|
||||
var err error
|
||||
extra, err = json.Marshal(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
marshalFunc := func(msg *logger.Message) ([]byte, error) {
|
||||
if err := marshalMessage(msg, extra, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := buf.Bytes()
|
||||
buf.Reset()
|
||||
return b, nil
|
||||
}
|
||||
|
||||
writer, err := loggerutils.NewLogFile(info.LogPath, capval, maxFiles, compress, marshalFunc, decodeFunc, 0640)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &JSONFileLogger{
|
||||
writer: writer,
|
||||
readers: make(map[*logger.LogWatcher]struct{}),
|
||||
tag: tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Log converts logger.Message to jsonlog.JSONLog and serializes it to file.
|
||||
func (l *JSONFileLogger) Log(msg *logger.Message) error {
|
||||
l.mu.Lock()
|
||||
err := l.writer.WriteLogEntry(msg)
|
||||
l.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func marshalMessage(msg *logger.Message, extra json.RawMessage, buf *bytes.Buffer) error {
|
||||
logLine := msg.Line
|
||||
if msg.PLogMetaData == nil || (msg.PLogMetaData != nil && msg.PLogMetaData.Last) {
|
||||
logLine = append(msg.Line, '\n')
|
||||
}
|
||||
err := (&jsonlog.JSONLogs{
|
||||
Log: logLine,
|
||||
Stream: msg.Source,
|
||||
Created: msg.Timestamp,
|
||||
RawAttrs: extra,
|
||||
}).MarshalJSONBuf(buf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error writing log message to buffer")
|
||||
}
|
||||
err = buf.WriteByte('\n')
|
||||
return errors.Wrap(err, "error finalizing log buffer")
|
||||
}
|
||||
|
||||
// ValidateLogOpt looks for json specific log options max-file & max-size.
|
||||
func ValidateLogOpt(cfg map[string]string) error {
|
||||
for key := range cfg {
|
||||
switch key {
|
||||
case "max-file":
|
||||
case "max-size":
|
||||
case "compress":
|
||||
case "labels":
|
||||
case "env":
|
||||
case "env-regex":
|
||||
case "tag":
|
||||
default:
|
||||
return fmt.Errorf("unknown log opt '%s' for json-file log driver", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes underlying file and signals all readers to stop.
|
||||
func (l *JSONFileLogger) Close() error {
|
||||
l.mu.Lock()
|
||||
l.closed = true
|
||||
err := l.writer.Close()
|
||||
for r := range l.readers {
|
||||
r.Close()
|
||||
delete(l.readers, r)
|
||||
}
|
||||
l.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// Name returns name of this logger.
|
||||
func (l *JSONFileLogger) Name() string {
|
||||
return Name
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package jsonlog // import "github.com/docker/docker/daemon/logger/jsonfilelog/jsonlog"
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// JSONLog is a log message, typically a single entry from a given log stream.
|
||||
type JSONLog struct {
|
||||
// Log is the log message
|
||||
Log string `json:"log,omitempty"`
|
||||
// Stream is the log source
|
||||
Stream string `json:"stream,omitempty"`
|
||||
// Created is the created timestamp of log
|
||||
Created time.Time `json:"time"`
|
||||
// Attrs is the list of extra attributes provided by the user
|
||||
Attrs map[string]string `json:"attrs,omitempty"`
|
||||
}
|
||||
|
||||
// Reset all fields to their zero value.
|
||||
func (jl *JSONLog) Reset() {
|
||||
jl.Log = ""
|
||||
jl.Stream = ""
|
||||
jl.Created = time.Time{}
|
||||
jl.Attrs = make(map[string]string)
|
||||
}
|
||||
Generated
Vendored
+125
@@ -0,0 +1,125 @@
|
||||
package jsonlog // import "github.com/docker/docker/daemon/logger/jsonfilelog/jsonlog"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// JSONLogs marshals encoded JSONLog objects
|
||||
type JSONLogs struct {
|
||||
Log []byte `json:"log,omitempty"`
|
||||
Stream string `json:"stream,omitempty"`
|
||||
Created time.Time `json:"time"`
|
||||
|
||||
// json-encoded bytes
|
||||
RawAttrs json.RawMessage `json:"attrs,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSONBuf is an optimized JSON marshaller that avoids reflection
|
||||
// and unnecessary allocation.
|
||||
func (mj *JSONLogs) MarshalJSONBuf(buf *bytes.Buffer) error {
|
||||
var first = true
|
||||
|
||||
buf.WriteString(`{`)
|
||||
if len(mj.Log) != 0 {
|
||||
first = false
|
||||
buf.WriteString(`"log":`)
|
||||
ffjsonWriteJSONBytesAsString(buf, mj.Log)
|
||||
}
|
||||
if len(mj.Stream) != 0 {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
buf.WriteString(`,`)
|
||||
}
|
||||
buf.WriteString(`"stream":`)
|
||||
ffjsonWriteJSONBytesAsString(buf, []byte(mj.Stream))
|
||||
}
|
||||
if len(mj.RawAttrs) > 0 {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
buf.WriteString(`,`)
|
||||
}
|
||||
buf.WriteString(`"attrs":`)
|
||||
buf.Write(mj.RawAttrs)
|
||||
}
|
||||
if !first {
|
||||
buf.WriteString(`,`)
|
||||
}
|
||||
|
||||
created, err := fastTimeMarshalJSON(mj.Created)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf.WriteString(`"time":`)
|
||||
buf.WriteString(created)
|
||||
buf.WriteString(`}`)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ffjsonWriteJSONBytesAsString(buf *bytes.Buffer, s []byte) {
|
||||
const hex = "0123456789abcdef"
|
||||
|
||||
buf.WriteByte('"')
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
buf.Write(s[start:i])
|
||||
}
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(b)
|
||||
case '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('n')
|
||||
case '\r':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('r')
|
||||
default:
|
||||
|
||||
buf.WriteString(`\u00`)
|
||||
buf.WriteByte(hex[b>>4])
|
||||
buf.WriteByte(hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRune(s[i:])
|
||||
if c == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
buf.Write(s[start:i])
|
||||
}
|
||||
buf.WriteString(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
if c == '\u2028' || c == '\u2029' {
|
||||
if start < i {
|
||||
buf.Write(s[start:i])
|
||||
}
|
||||
buf.WriteString(`\u202`)
|
||||
buf.WriteByte(hex[c&0xF])
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
buf.Write(s[start:])
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
}
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
package jsonlog // import "github.com/docker/docker/daemon/logger/jsonfilelog/jsonlog"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const jsonFormat = `"` + time.RFC3339Nano + `"`
|
||||
|
||||
// fastTimeMarshalJSON avoids one of the extra allocations that
|
||||
// time.MarshalJSON is making.
|
||||
func fastTimeMarshalJSON(t time.Time) (string, error) {
|
||||
if y := t.Year(); y < 0 || y >= 10000 {
|
||||
// RFC 3339 is clear that years are 4 digits exactly.
|
||||
// See golang.org/issue/4556#c15 for more discussion.
|
||||
return "", errors.New("time.MarshalJSON: year outside of range [0,9999]")
|
||||
}
|
||||
return t.Format(jsonFormat), nil
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package jsonfilelog // import "github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/daemon/logger"
|
||||
"github.com/docker/docker/daemon/logger/jsonfilelog/jsonlog"
|
||||
)
|
||||
|
||||
const maxJSONDecodeRetry = 20000
|
||||
|
||||
// ReadLogs implements the logger's LogReader interface for the logs
|
||||
// created by this driver.
|
||||
func (l *JSONFileLogger) ReadLogs(config logger.ReadConfig) *logger.LogWatcher {
|
||||
logWatcher := logger.NewLogWatcher()
|
||||
|
||||
go l.readLogs(logWatcher, config)
|
||||
return logWatcher
|
||||
}
|
||||
|
||||
func (l *JSONFileLogger) readLogs(watcher *logger.LogWatcher, config logger.ReadConfig) {
|
||||
defer close(watcher.Msg)
|
||||
|
||||
l.mu.Lock()
|
||||
l.readers[watcher] = struct{}{}
|
||||
l.mu.Unlock()
|
||||
|
||||
l.writer.ReadLogs(config, watcher)
|
||||
|
||||
l.mu.Lock()
|
||||
delete(l.readers, watcher)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func decodeLogLine(dec *json.Decoder, l *jsonlog.JSONLog) (*logger.Message, error) {
|
||||
l.Reset()
|
||||
if err := dec.Decode(l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var attrs []backend.LogAttr
|
||||
if len(l.Attrs) != 0 {
|
||||
attrs = make([]backend.LogAttr, 0, len(l.Attrs))
|
||||
for k, v := range l.Attrs {
|
||||
attrs = append(attrs, backend.LogAttr{Key: k, Value: v})
|
||||
}
|
||||
}
|
||||
msg := &logger.Message{
|
||||
Source: l.Stream,
|
||||
Timestamp: l.Created,
|
||||
Line: []byte(l.Log),
|
||||
Attrs: attrs,
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// decodeFunc is used to create a decoder for the log file reader
|
||||
func decodeFunc(rdr io.Reader) func() (*logger.Message, error) {
|
||||
l := &jsonlog.JSONLog{}
|
||||
dec := json.NewDecoder(rdr)
|
||||
return func() (msg *logger.Message, err error) {
|
||||
for retries := 0; retries < maxJSONDecodeRetry; retries++ {
|
||||
msg, err = decodeLogLine(dec, l)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// try again, could be due to a an incomplete json object as we read
|
||||
if _, ok := err.(*json.SyntaxError); ok {
|
||||
dec = json.NewDecoder(rdr)
|
||||
retries++
|
||||
continue
|
||||
}
|
||||
|
||||
// io.ErrUnexpectedEOF is returned from json.Decoder when there is
|
||||
// remaining data in the parser's buffer while an io.EOF occurs.
|
||||
// If the json logger writes a partial json log entry to the disk
|
||||
// while at the same time the decoder tries to decode it, the race condition happens.
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
reader := io.MultiReader(dec.Buffered(), rdr)
|
||||
dec = json.NewDecoder(reader)
|
||||
retries++
|
||||
}
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// Package logger defines interfaces that logger drivers implement to
|
||||
// log messages.
|
||||
//
|
||||
// The other half of a logger driver is the implementation of the
|
||||
// factory, which holds the contextual instance information that
|
||||
// allows multiple loggers of the same type to perform different
|
||||
// actions, such as logging to different locations.
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
)
|
||||
|
||||
// ErrReadLogsNotSupported is returned when the underlying log driver does not support reading
|
||||
type ErrReadLogsNotSupported struct{}
|
||||
|
||||
func (ErrReadLogsNotSupported) Error() string {
|
||||
return "configured logging driver does not support reading"
|
||||
}
|
||||
|
||||
// NotImplemented makes this error implement the `NotImplemented` interface from api/errdefs
|
||||
func (ErrReadLogsNotSupported) NotImplemented() {}
|
||||
|
||||
const (
|
||||
logWatcherBufferSize = 4096
|
||||
)
|
||||
|
||||
var messagePool = &sync.Pool{New: func() interface{} { return &Message{Line: make([]byte, 0, 256)} }}
|
||||
|
||||
// NewMessage returns a new message from the message sync.Pool
|
||||
func NewMessage() *Message {
|
||||
return messagePool.Get().(*Message)
|
||||
}
|
||||
|
||||
// PutMessage puts the specified message back n the message pool.
|
||||
// The message fields are reset before putting into the pool.
|
||||
func PutMessage(msg *Message) {
|
||||
msg.reset()
|
||||
messagePool.Put(msg)
|
||||
}
|
||||
|
||||
// Message is data structure that represents piece of output produced by some
|
||||
// container. The Line member is a slice of an array whose contents can be
|
||||
// changed after a log driver's Log() method returns.
|
||||
//
|
||||
// Message is subtyped from backend.LogMessage because there is a lot of
|
||||
// internal complexity around the Message type that should not be exposed
|
||||
// to any package not explicitly importing the logger type.
|
||||
//
|
||||
// Any changes made to this struct must also be updated in the `reset` function
|
||||
type Message backend.LogMessage
|
||||
|
||||
// reset sets the message back to default values
|
||||
// This is used when putting a message back into the message pool.
|
||||
// Any changes to the `Message` struct should be reflected here.
|
||||
func (m *Message) reset() {
|
||||
m.Line = m.Line[:0]
|
||||
m.Source = ""
|
||||
m.Attrs = nil
|
||||
m.PLogMetaData = nil
|
||||
|
||||
m.Err = nil
|
||||
}
|
||||
|
||||
// AsLogMessage returns a pointer to the message as a pointer to
|
||||
// backend.LogMessage, which is an identical type with a different purpose
|
||||
func (m *Message) AsLogMessage() *backend.LogMessage {
|
||||
return (*backend.LogMessage)(m)
|
||||
}
|
||||
|
||||
// Logger is the interface for docker logging drivers.
|
||||
type Logger interface {
|
||||
Log(*Message) error
|
||||
Name() string
|
||||
Close() error
|
||||
}
|
||||
|
||||
// SizedLogger is the interface for logging drivers that can control
|
||||
// the size of buffer used for their messages.
|
||||
type SizedLogger interface {
|
||||
Logger
|
||||
BufSize() int
|
||||
}
|
||||
|
||||
// ReadConfig is the configuration passed into ReadLogs.
|
||||
type ReadConfig struct {
|
||||
Since time.Time
|
||||
Until time.Time
|
||||
Tail int
|
||||
Follow bool
|
||||
}
|
||||
|
||||
// LogReader is the interface for reading log messages for loggers that support reading.
|
||||
type LogReader interface {
|
||||
// Read logs from underlying logging backend
|
||||
ReadLogs(ReadConfig) *LogWatcher
|
||||
}
|
||||
|
||||
// LogWatcher is used when consuming logs read from the LogReader interface.
|
||||
type LogWatcher struct {
|
||||
// For sending log messages to a reader.
|
||||
Msg chan *Message
|
||||
// For sending error messages that occur while while reading logs.
|
||||
Err chan error
|
||||
closeOnce sync.Once
|
||||
closeNotifier chan struct{}
|
||||
}
|
||||
|
||||
// NewLogWatcher returns a new LogWatcher.
|
||||
func NewLogWatcher() *LogWatcher {
|
||||
return &LogWatcher{
|
||||
Msg: make(chan *Message, logWatcherBufferSize),
|
||||
Err: make(chan error, 1),
|
||||
closeNotifier: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Close notifies the underlying log reader to stop.
|
||||
func (w *LogWatcher) Close() {
|
||||
// only close if not already closed
|
||||
w.closeOnce.Do(func() {
|
||||
close(w.closeNotifier)
|
||||
})
|
||||
}
|
||||
|
||||
// WatchClose returns a channel receiver that receives notification
|
||||
// when the watcher has been closed. This should only be called from
|
||||
// one goroutine.
|
||||
func (w *LogWatcher) WatchClose() <-chan struct{} {
|
||||
return w.closeNotifier
|
||||
}
|
||||
|
||||
// Capability defines the list of capabilities that a driver can implement
|
||||
// These capabilities are not required to be a logging driver, however do
|
||||
// determine how a logging driver can be used
|
||||
type Capability struct {
|
||||
// Determines if a log driver can read back logs
|
||||
ReadLogs bool
|
||||
}
|
||||
|
||||
// MarshalFunc is a func that marshals a message into an arbitrary format
|
||||
type MarshalFunc func(*Message) ([]byte, error)
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/docker/docker/daemon/logger"
|
||||
"github.com/docker/docker/daemon/logger/templates"
|
||||
)
|
||||
|
||||
// DefaultTemplate defines the defaults template logger should use.
|
||||
const DefaultTemplate = "{{.ID}}"
|
||||
|
||||
// ParseLogTag generates a context aware tag for consistency across different
|
||||
// log drivers based on the context of the running container.
|
||||
func ParseLogTag(info logger.Info, defaultTemplate string) (string, error) {
|
||||
tagTemplate := info.Config["tag"]
|
||||
if tagTemplate == "" {
|
||||
tagTemplate = defaultTemplate
|
||||
}
|
||||
|
||||
tmpl, err := templates.NewParse("log-tag", tagTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := tmpl.Execute(buf, &info); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
+656
@@ -0,0 +1,656 @@
|
||||
package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/daemon/logger"
|
||||
"github.com/docker/docker/daemon/logger/loggerutils/multireader"
|
||||
"github.com/docker/docker/pkg/filenotify"
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/pubsub"
|
||||
"github.com/docker/docker/pkg/tailfile"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const tmpLogfileSuffix = ".tmp"
|
||||
|
||||
// rotateFileMetadata is a metadata of the gzip header of the compressed log file
|
||||
type rotateFileMetadata struct {
|
||||
LastTime time.Time `json:"lastTime,omitempty"`
|
||||
}
|
||||
|
||||
// refCounter is a counter of logfile being referenced
|
||||
type refCounter struct {
|
||||
mu sync.Mutex
|
||||
counter map[string]int
|
||||
}
|
||||
|
||||
// Reference increase the reference counter for specified logfile
|
||||
func (rc *refCounter) GetReference(fileName string, openRefFile func(fileName string, exists bool) (*os.File, error)) (*os.File, error) {
|
||||
rc.mu.Lock()
|
||||
defer rc.mu.Unlock()
|
||||
|
||||
var (
|
||||
file *os.File
|
||||
err error
|
||||
)
|
||||
_, ok := rc.counter[fileName]
|
||||
file, err = openRefFile(fileName, ok)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
rc.counter[fileName]++
|
||||
} else if file != nil {
|
||||
rc.counter[file.Name()] = 1
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Dereference reduce the reference counter for specified logfile
|
||||
func (rc *refCounter) Dereference(fileName string) error {
|
||||
rc.mu.Lock()
|
||||
defer rc.mu.Unlock()
|
||||
|
||||
rc.counter[fileName]--
|
||||
if rc.counter[fileName] <= 0 {
|
||||
delete(rc.counter, fileName)
|
||||
err := os.Remove(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogFile is Logger implementation for default Docker logging.
|
||||
type LogFile struct {
|
||||
mu sync.RWMutex // protects the logfile access
|
||||
f *os.File // store for closing
|
||||
closed bool
|
||||
rotateMu sync.Mutex // blocks the next rotation until the current rotation is completed
|
||||
capacity int64 // maximum size of each file
|
||||
currentSize int64 // current size of the latest file
|
||||
maxFiles int // maximum number of files
|
||||
compress bool // whether old versions of log files are compressed
|
||||
lastTimestamp time.Time // timestamp of the last log
|
||||
filesRefCounter refCounter // keep reference-counted of decompressed files
|
||||
notifyRotate *pubsub.Publisher
|
||||
marshal logger.MarshalFunc
|
||||
createDecoder makeDecoderFunc
|
||||
perms os.FileMode
|
||||
}
|
||||
|
||||
type makeDecoderFunc func(rdr io.Reader) func() (*logger.Message, error)
|
||||
|
||||
//NewLogFile creates new LogFile
|
||||
func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, marshaller logger.MarshalFunc, decodeFunc makeDecoderFunc, perms os.FileMode) (*LogFile, error) {
|
||||
log, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, perms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size, err := log.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LogFile{
|
||||
f: log,
|
||||
capacity: capacity,
|
||||
currentSize: size,
|
||||
maxFiles: maxFiles,
|
||||
compress: compress,
|
||||
filesRefCounter: refCounter{counter: make(map[string]int)},
|
||||
notifyRotate: pubsub.NewPublisher(0, 1),
|
||||
marshal: marshaller,
|
||||
createDecoder: decodeFunc,
|
||||
perms: perms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WriteLogEntry writes the provided log message to the current log file.
|
||||
// This may trigger a rotation event if the max file/capacity limits are hit.
|
||||
func (w *LogFile) WriteLogEntry(msg *logger.Message) error {
|
||||
b, err := w.marshal(msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error marshalling log message")
|
||||
}
|
||||
|
||||
logger.PutMessage(msg)
|
||||
|
||||
w.mu.Lock()
|
||||
if w.closed {
|
||||
w.mu.Unlock()
|
||||
return errors.New("cannot write because the output file was closed")
|
||||
}
|
||||
|
||||
if err := w.checkCapacityAndRotate(); err != nil {
|
||||
w.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := w.f.Write(b)
|
||||
if err == nil {
|
||||
w.currentSize += int64(n)
|
||||
w.lastTimestamp = msg.Timestamp
|
||||
}
|
||||
w.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *LogFile) checkCapacityAndRotate() error {
|
||||
if w.capacity == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if w.currentSize >= w.capacity {
|
||||
w.rotateMu.Lock()
|
||||
fname := w.f.Name()
|
||||
if err := w.f.Close(); err != nil {
|
||||
w.rotateMu.Unlock()
|
||||
return errors.Wrap(err, "error closing file")
|
||||
}
|
||||
if err := rotate(fname, w.maxFiles, w.compress); err != nil {
|
||||
w.rotateMu.Unlock()
|
||||
return err
|
||||
}
|
||||
file, err := os.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms)
|
||||
if err != nil {
|
||||
w.rotateMu.Unlock()
|
||||
return err
|
||||
}
|
||||
w.f = file
|
||||
w.currentSize = 0
|
||||
w.notifyRotate.Publish(struct{}{})
|
||||
|
||||
if w.maxFiles <= 1 || !w.compress {
|
||||
w.rotateMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
compressFile(fname+".1", w.lastTimestamp)
|
||||
w.rotateMu.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rotate(name string, maxFiles int, compress bool) error {
|
||||
if maxFiles < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var extension string
|
||||
if compress {
|
||||
extension = ".gz"
|
||||
}
|
||||
for i := maxFiles - 1; i > 1; i-- {
|
||||
toPath := name + "." + strconv.Itoa(i) + extension
|
||||
fromPath := name + "." + strconv.Itoa(i-1) + extension
|
||||
if err := os.Rename(fromPath, toPath); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Rename(name, name+".1"); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func compressFile(fileName string, lastTimestamp time.Time) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to open log file: %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
file.Close()
|
||||
err := os.Remove(fileName)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to remove source log file: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
outFile, err := os.OpenFile(fileName+".gz", os.O_CREATE|os.O_RDWR, 0640)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to open or create gzip log file: %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
outFile.Close()
|
||||
if err != nil {
|
||||
os.Remove(fileName + ".gz")
|
||||
}
|
||||
}()
|
||||
|
||||
compressWriter := gzip.NewWriter(outFile)
|
||||
defer compressWriter.Close()
|
||||
|
||||
// Add the last log entry timestramp to the gzip header
|
||||
extra := rotateFileMetadata{}
|
||||
extra.LastTime = lastTimestamp
|
||||
compressWriter.Header.Extra, err = json.Marshal(&extra)
|
||||
if err != nil {
|
||||
// Here log the error only and don't return since this is just an optimization.
|
||||
logrus.Warningf("Failed to marshal JSON: %v", err)
|
||||
}
|
||||
|
||||
_, err = pools.Copy(compressWriter, file)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("module", "container.logs").WithField("file", fileName).Error("Error compressing log file")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// MaxFiles return maximum number of files
|
||||
func (w *LogFile) MaxFiles() int {
|
||||
return w.maxFiles
|
||||
}
|
||||
|
||||
// Close closes underlying file and signals all readers to stop.
|
||||
func (w *LogFile) Close() error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if w.closed {
|
||||
return nil
|
||||
}
|
||||
if err := w.f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadLogs decodes entries from log files and sends them the passed in watcher
|
||||
func (w *LogFile) ReadLogs(config logger.ReadConfig, watcher *logger.LogWatcher) {
|
||||
w.mu.RLock()
|
||||
currentFile, err := os.Open(w.f.Name())
|
||||
if err != nil {
|
||||
w.mu.RUnlock()
|
||||
watcher.Err <- err
|
||||
return
|
||||
}
|
||||
defer currentFile.Close()
|
||||
|
||||
currentChunk, err := newSectionReader(currentFile)
|
||||
if err != nil {
|
||||
w.mu.RUnlock()
|
||||
watcher.Err <- err
|
||||
return
|
||||
}
|
||||
|
||||
if config.Tail != 0 {
|
||||
files, err := w.openRotatedFiles(config)
|
||||
if err != nil {
|
||||
w.mu.RUnlock()
|
||||
watcher.Err <- err
|
||||
return
|
||||
}
|
||||
w.mu.RUnlock()
|
||||
seekers := make([]io.ReadSeeker, 0, len(files)+1)
|
||||
for _, f := range files {
|
||||
seekers = append(seekers, f)
|
||||
}
|
||||
if currentChunk.Size() > 0 {
|
||||
seekers = append(seekers, currentChunk)
|
||||
}
|
||||
if len(seekers) > 0 {
|
||||
tailFile(multireader.MultiReadSeeker(seekers...), watcher, w.createDecoder, config)
|
||||
}
|
||||
for _, f := range files {
|
||||
f.Close()
|
||||
fileName := f.Name()
|
||||
if strings.HasSuffix(fileName, tmpLogfileSuffix) {
|
||||
err := w.filesRefCounter.Dereference(fileName)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to dereference the log file %q: %v", fileName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.mu.RLock()
|
||||
}
|
||||
|
||||
if !config.Follow || w.closed {
|
||||
w.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
w.mu.RUnlock()
|
||||
|
||||
notifyRotate := w.notifyRotate.Subscribe()
|
||||
defer w.notifyRotate.Evict(notifyRotate)
|
||||
followLogs(currentFile, watcher, notifyRotate, w.createDecoder, config.Since, config.Until)
|
||||
}
|
||||
|
||||
func (w *LogFile) openRotatedFiles(config logger.ReadConfig) (files []*os.File, err error) {
|
||||
w.rotateMu.Lock()
|
||||
defer w.rotateMu.Unlock()
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
for _, f := range files {
|
||||
f.Close()
|
||||
if strings.HasSuffix(f.Name(), tmpLogfileSuffix) {
|
||||
err := os.Remove(f.Name())
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
logrus.Warningf("Failed to remove the logfile %q: %v", f.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for i := w.maxFiles; i > 1; i-- {
|
||||
f, err := os.Open(fmt.Sprintf("%s.%d", w.f.Name(), i-1))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("%s.%d.gz", w.f.Name(), i-1)
|
||||
decompressedFileName := fileName + tmpLogfileSuffix
|
||||
tmpFile, err := w.filesRefCounter.GetReference(decompressedFileName, func(refFileName string, exists bool) (*os.File, error) {
|
||||
if exists {
|
||||
return os.Open(refFileName)
|
||||
}
|
||||
return decompressfile(fileName, refFileName, config.Since)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if tmpFile == nil {
|
||||
// The log before `config.Since` does not need to read
|
||||
break
|
||||
}
|
||||
|
||||
files = append(files, tmpFile)
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func decompressfile(fileName, destFileName string, since time.Time) (*os.File, error) {
|
||||
cf, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cf.Close()
|
||||
|
||||
rc, err := gzip.NewReader(cf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// Extract the last log entry timestramp from the gzip header
|
||||
extra := &rotateFileMetadata{}
|
||||
err = json.Unmarshal(rc.Header.Extra, extra)
|
||||
if err == nil && extra.LastTime.Before(since) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rs, err := os.OpenFile(destFileName, os.O_CREATE|os.O_RDWR, 0640)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = pools.Copy(rs, rc)
|
||||
if err != nil {
|
||||
rs.Close()
|
||||
rErr := os.Remove(rs.Name())
|
||||
if rErr != nil && os.IsNotExist(rErr) {
|
||||
logrus.Errorf("Failed to remove the logfile %q: %v", rs.Name(), rErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func newSectionReader(f *os.File) (*io.SectionReader, error) {
|
||||
// seek to the end to get the size
|
||||
// we'll leave this at the end of the file since section reader does not advance the reader
|
||||
size, err := f.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error getting current file size")
|
||||
}
|
||||
return io.NewSectionReader(f, 0, size), nil
|
||||
}
|
||||
|
||||
type decodeFunc func() (*logger.Message, error)
|
||||
|
||||
func tailFile(f io.ReadSeeker, watcher *logger.LogWatcher, createDecoder makeDecoderFunc, config logger.ReadConfig) {
|
||||
var rdr io.Reader = f
|
||||
if config.Tail > 0 {
|
||||
ls, err := tailfile.TailFile(f, config.Tail)
|
||||
if err != nil {
|
||||
watcher.Err <- err
|
||||
return
|
||||
}
|
||||
rdr = bytes.NewBuffer(bytes.Join(ls, []byte("\n")))
|
||||
}
|
||||
|
||||
decodeLogLine := createDecoder(rdr)
|
||||
for {
|
||||
msg, err := decodeLogLine()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
watcher.Err <- err
|
||||
}
|
||||
return
|
||||
}
|
||||
if !config.Since.IsZero() && msg.Timestamp.Before(config.Since) {
|
||||
continue
|
||||
}
|
||||
if !config.Until.IsZero() && msg.Timestamp.After(config.Until) {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-watcher.WatchClose():
|
||||
return
|
||||
case watcher.Msg <- msg:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func followLogs(f *os.File, logWatcher *logger.LogWatcher, notifyRotate chan interface{}, createDecoder makeDecoderFunc, since, until time.Time) {
|
||||
decodeLogLine := createDecoder(f)
|
||||
|
||||
name := f.Name()
|
||||
fileWatcher, err := watchFile(name)
|
||||
if err != nil {
|
||||
logWatcher.Err <- err
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
fileWatcher.Remove(name)
|
||||
fileWatcher.Close()
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go func() {
|
||||
select {
|
||||
case <-logWatcher.WatchClose():
|
||||
fileWatcher.Remove(name)
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
var retries int
|
||||
handleRotate := func() error {
|
||||
f.Close()
|
||||
fileWatcher.Remove(name)
|
||||
|
||||
// retry when the file doesn't exist
|
||||
for retries := 0; retries <= 5; retries++ {
|
||||
f, err = os.Open(name)
|
||||
if err == nil || !os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileWatcher.Add(name); err != nil {
|
||||
return err
|
||||
}
|
||||
decodeLogLine = createDecoder(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
errRetry := errors.New("retry")
|
||||
errDone := errors.New("done")
|
||||
waitRead := func() error {
|
||||
select {
|
||||
case e := <-fileWatcher.Events():
|
||||
switch e.Op {
|
||||
case fsnotify.Write:
|
||||
decodeLogLine = createDecoder(f)
|
||||
return nil
|
||||
case fsnotify.Rename, fsnotify.Remove:
|
||||
select {
|
||||
case <-notifyRotate:
|
||||
case <-ctx.Done():
|
||||
return errDone
|
||||
}
|
||||
if err := handleRotate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errRetry
|
||||
case err := <-fileWatcher.Errors():
|
||||
logrus.Debug("logger got error watching file: %v", err)
|
||||
// Something happened, let's try and stay alive and create a new watcher
|
||||
if retries <= 5 {
|
||||
fileWatcher.Close()
|
||||
fileWatcher, err = watchFile(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
retries++
|
||||
return errRetry
|
||||
}
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return errDone
|
||||
}
|
||||
}
|
||||
|
||||
handleDecodeErr := func(err error) error {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
err := waitRead()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err == errRetry {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// main loop
|
||||
for {
|
||||
msg, err := decodeLogLine()
|
||||
if err != nil {
|
||||
if err := handleDecodeErr(err); err != nil {
|
||||
if err == errDone {
|
||||
return
|
||||
}
|
||||
// we got an unrecoverable error, so return
|
||||
logWatcher.Err <- err
|
||||
return
|
||||
}
|
||||
// ready to try again
|
||||
continue
|
||||
}
|
||||
|
||||
retries = 0 // reset retries since we've succeeded
|
||||
if !since.IsZero() && msg.Timestamp.Before(since) {
|
||||
continue
|
||||
}
|
||||
if !until.IsZero() && msg.Timestamp.After(until) {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case logWatcher.Msg <- msg:
|
||||
case <-ctx.Done():
|
||||
logWatcher.Msg <- msg
|
||||
for {
|
||||
msg, err := decodeLogLine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !since.IsZero() && msg.Timestamp.Before(since) {
|
||||
continue
|
||||
}
|
||||
if !until.IsZero() && msg.Timestamp.After(until) {
|
||||
return
|
||||
}
|
||||
logWatcher.Msg <- msg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func watchFile(name string) (filenotify.FileWatcher, error) {
|
||||
fileWatcher, err := filenotify.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"module": "logger",
|
||||
"fille": name,
|
||||
})
|
||||
|
||||
if err := fileWatcher.Add(name); err != nil {
|
||||
logger.WithError(err).Warnf("falling back to file poller")
|
||||
fileWatcher.Close()
|
||||
fileWatcher = filenotify.NewPollingWatcher()
|
||||
|
||||
if err := fileWatcher.Add(name); err != nil {
|
||||
fileWatcher.Close()
|
||||
logger.WithError(err).Debugf("error watching log file for modifications")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return fileWatcher, nil
|
||||
}
|
||||
Generated
Vendored
+212
@@ -0,0 +1,212 @@
|
||||
package multireader // import "github.com/docker/docker/daemon/logger/loggerutils/multireader"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type pos struct {
|
||||
idx int
|
||||
offset int64
|
||||
}
|
||||
|
||||
type multiReadSeeker struct {
|
||||
readers []io.ReadSeeker
|
||||
pos *pos
|
||||
posIdx map[io.ReadSeeker]int
|
||||
}
|
||||
|
||||
func (r *multiReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||
var tmpOffset int64
|
||||
switch whence {
|
||||
case os.SEEK_SET:
|
||||
for i, rdr := range r.readers {
|
||||
// get size of the current reader
|
||||
s, err := rdr.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if offset > tmpOffset+s {
|
||||
if i == len(r.readers)-1 {
|
||||
rdrOffset := s + (offset - tmpOffset)
|
||||
if _, err := rdr.Seek(rdrOffset, os.SEEK_SET); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
r.pos = &pos{i, rdrOffset}
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
tmpOffset += s
|
||||
continue
|
||||
}
|
||||
|
||||
rdrOffset := offset - tmpOffset
|
||||
idx := i
|
||||
|
||||
if _, err := rdr.Seek(rdrOffset, os.SEEK_SET); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
// make sure all following readers are at 0
|
||||
for _, rdr := range r.readers[i+1:] {
|
||||
rdr.Seek(0, os.SEEK_SET)
|
||||
}
|
||||
|
||||
if rdrOffset == s && i != len(r.readers)-1 {
|
||||
idx++
|
||||
rdrOffset = 0
|
||||
}
|
||||
r.pos = &pos{idx, rdrOffset}
|
||||
return offset, nil
|
||||
}
|
||||
case os.SEEK_END:
|
||||
for _, rdr := range r.readers {
|
||||
s, err := rdr.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
tmpOffset += s
|
||||
}
|
||||
if _, err := r.Seek(tmpOffset+offset, os.SEEK_SET); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return tmpOffset + offset, nil
|
||||
case os.SEEK_CUR:
|
||||
if r.pos == nil {
|
||||
return r.Seek(offset, os.SEEK_SET)
|
||||
}
|
||||
// Just return the current offset
|
||||
if offset == 0 {
|
||||
return r.getCurOffset()
|
||||
}
|
||||
|
||||
curOffset, err := r.getCurOffset()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
rdr, rdrOffset, err := r.getReaderForOffset(curOffset + offset)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
r.pos = &pos{r.posIdx[rdr], rdrOffset}
|
||||
return curOffset + offset, nil
|
||||
default:
|
||||
return -1, fmt.Errorf("Invalid whence: %d", whence)
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("Error seeking for whence: %d, offset: %d", whence, offset)
|
||||
}
|
||||
|
||||
func (r *multiReadSeeker) getReaderForOffset(offset int64) (io.ReadSeeker, int64, error) {
|
||||
|
||||
var offsetTo int64
|
||||
|
||||
for _, rdr := range r.readers {
|
||||
size, err := getReadSeekerSize(rdr)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if offsetTo+size > offset {
|
||||
return rdr, offset - offsetTo, nil
|
||||
}
|
||||
if rdr == r.readers[len(r.readers)-1] {
|
||||
return rdr, offsetTo + offset, nil
|
||||
}
|
||||
offsetTo += size
|
||||
}
|
||||
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (r *multiReadSeeker) getCurOffset() (int64, error) {
|
||||
var totalSize int64
|
||||
for _, rdr := range r.readers[:r.pos.idx+1] {
|
||||
if r.posIdx[rdr] == r.pos.idx {
|
||||
totalSize += r.pos.offset
|
||||
break
|
||||
}
|
||||
|
||||
size, err := getReadSeekerSize(rdr)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error getting seeker size: %v", err)
|
||||
}
|
||||
totalSize += size
|
||||
}
|
||||
return totalSize, nil
|
||||
}
|
||||
|
||||
func (r *multiReadSeeker) Read(b []byte) (int, error) {
|
||||
if r.pos == nil {
|
||||
// make sure all readers are at 0
|
||||
r.Seek(0, os.SEEK_SET)
|
||||
}
|
||||
|
||||
bLen := int64(len(b))
|
||||
buf := bytes.NewBuffer(nil)
|
||||
var rdr io.ReadSeeker
|
||||
|
||||
for _, rdr = range r.readers[r.pos.idx:] {
|
||||
readBytes, err := io.CopyN(buf, rdr, bLen)
|
||||
if err != nil && err != io.EOF {
|
||||
return -1, err
|
||||
}
|
||||
bLen -= readBytes
|
||||
|
||||
if bLen == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rdrPos, err := rdr.Seek(0, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
r.pos = &pos{r.posIdx[rdr], rdrPos}
|
||||
return buf.Read(b)
|
||||
}
|
||||
|
||||
func getReadSeekerSize(rdr io.ReadSeeker) (int64, error) {
|
||||
// save the current position
|
||||
pos, err := rdr.Seek(0, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// get the size
|
||||
size, err := rdr.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// reset the position
|
||||
if _, err := rdr.Seek(pos, os.SEEK_SET); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// MultiReadSeeker returns a ReadSeeker that's the logical concatenation of the provided
|
||||
// input readseekers. After calling this method the initial position is set to the
|
||||
// beginning of the first ReadSeeker. At the end of a ReadSeeker, Read always advances
|
||||
// to the beginning of the next ReadSeeker and returns EOF at the end of the last ReadSeeker.
|
||||
// Seek can be used over the sum of lengths of all readseekers.
|
||||
//
|
||||
// When a MultiReadSeeker is used, no Read and Seek operations should be made on
|
||||
// its ReadSeeker components. Also, users should make no assumption on the state
|
||||
// of individual readseekers while the MultiReadSeeker is used.
|
||||
func MultiReadSeeker(readers ...io.ReadSeeker) io.ReadSeeker {
|
||||
if len(readers) == 1 {
|
||||
return readers[0]
|
||||
}
|
||||
idx := make(map[io.ReadSeeker]int)
|
||||
for i, rdr := range readers {
|
||||
idx[rdr] = i
|
||||
}
|
||||
return &multiReadSeeker{
|
||||
readers: readers,
|
||||
posIdx: idx,
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Info provides enough information for a logging driver to do its function.
|
||||
type Info struct {
|
||||
Config map[string]string
|
||||
ContainerID string
|
||||
ContainerName string
|
||||
ContainerEntrypoint string
|
||||
ContainerArgs []string
|
||||
ContainerImageID string
|
||||
ContainerImageName string
|
||||
ContainerCreated time.Time
|
||||
ContainerEnv []string
|
||||
ContainerLabels map[string]string
|
||||
LogPath string
|
||||
DaemonName string
|
||||
}
|
||||
|
||||
// ExtraAttributes returns the user-defined extra attributes (labels,
|
||||
// environment variables) in key-value format. This can be used by log drivers
|
||||
// that support metadata to add more context to a log.
|
||||
func (info *Info) ExtraAttributes(keyMod func(string) string) (map[string]string, error) {
|
||||
extra := make(map[string]string)
|
||||
labels, ok := info.Config["labels"]
|
||||
if ok && len(labels) > 0 {
|
||||
for _, l := range strings.Split(labels, ",") {
|
||||
if v, ok := info.ContainerLabels[l]; ok {
|
||||
if keyMod != nil {
|
||||
l = keyMod(l)
|
||||
}
|
||||
extra[l] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
envMapping := make(map[string]string)
|
||||
for _, e := range info.ContainerEnv {
|
||||
if kv := strings.SplitN(e, "=", 2); len(kv) == 2 {
|
||||
envMapping[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
|
||||
env, ok := info.Config["env"]
|
||||
if ok && len(env) > 0 {
|
||||
for _, l := range strings.Split(env, ",") {
|
||||
if v, ok := envMapping[l]; ok {
|
||||
if keyMod != nil {
|
||||
l = keyMod(l)
|
||||
}
|
||||
extra[l] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
envRegex, ok := info.Config["env-regex"]
|
||||
if ok && len(envRegex) > 0 {
|
||||
re, err := regexp.Compile(envRegex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range envMapping {
|
||||
if re.MatchString(k) {
|
||||
if keyMod != nil {
|
||||
k = keyMod(k)
|
||||
}
|
||||
extra[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
// Hostname returns the hostname from the underlying OS.
|
||||
func (info *Info) Hostname() (string, error) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("logger: can not resolve hostname: %v", err)
|
||||
}
|
||||
return hostname, nil
|
||||
}
|
||||
|
||||
// Command returns the command that the container being logged was
|
||||
// started with. The Entrypoint is prepended to the container
|
||||
// arguments.
|
||||
func (info *Info) Command() string {
|
||||
terms := []string{info.ContainerEntrypoint}
|
||||
terms = append(terms, info.ContainerArgs...)
|
||||
command := strings.Join(terms, " ")
|
||||
return command
|
||||
}
|
||||
|
||||
// ID Returns the Container ID shortened to 12 characters.
|
||||
func (info *Info) ID() string {
|
||||
return info.ContainerID[:12]
|
||||
}
|
||||
|
||||
// FullID is an alias of ContainerID.
|
||||
func (info *Info) FullID() string {
|
||||
return info.ContainerID
|
||||
}
|
||||
|
||||
// Name returns the ContainerName without a preceding '/'.
|
||||
func (info *Info) Name() string {
|
||||
return strings.TrimPrefix(info.ContainerName, "/")
|
||||
}
|
||||
|
||||
// ImageID returns the ContainerImageID shortened to 12 characters.
|
||||
func (info *Info) ImageID() string {
|
||||
return info.ContainerImageID[:12]
|
||||
}
|
||||
|
||||
// ImageFullID is an alias of ContainerImageID.
|
||||
func (info *Info) ImageFullID() string {
|
||||
return info.ContainerImageID
|
||||
}
|
||||
|
||||
// ImageName is an alias of ContainerImageName
|
||||
func (info *Info) ImageName() string {
|
||||
return info.ContainerImageName
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/docker/api/types/plugins/logdriver"
|
||||
getter "github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var pluginGetter getter.PluginGetter
|
||||
|
||||
const extName = "LogDriver"
|
||||
|
||||
// logPlugin defines the available functions that logging plugins must implement.
|
||||
type logPlugin interface {
|
||||
StartLogging(streamPath string, info Info) (err error)
|
||||
StopLogging(streamPath string) (err error)
|
||||
Capabilities() (cap Capability, err error)
|
||||
ReadLogs(info Info, config ReadConfig) (stream io.ReadCloser, err error)
|
||||
}
|
||||
|
||||
// RegisterPluginGetter sets the plugingetter
|
||||
func RegisterPluginGetter(plugingetter getter.PluginGetter) {
|
||||
pluginGetter = plugingetter
|
||||
}
|
||||
|
||||
// GetDriver returns a logging driver by its name.
|
||||
// If the driver is empty, it looks for the local driver.
|
||||
func getPlugin(name string, mode int) (Creator, error) {
|
||||
p, err := pluginGetter.Get(name, extName, mode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error looking up logging plugin %s: %v", name, err)
|
||||
}
|
||||
|
||||
d := &logPluginProxy{p.Client()}
|
||||
return makePluginCreator(name, d, p.ScopedPath), nil
|
||||
}
|
||||
|
||||
func makePluginCreator(name string, l *logPluginProxy, scopePath func(s string) string) Creator {
|
||||
return func(logCtx Info) (logger Logger, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
pluginGetter.Get(name, extName, getter.Release)
|
||||
}
|
||||
}()
|
||||
|
||||
unscopedPath := filepath.Join("/", "run", "docker", "logging")
|
||||
logRoot := scopePath(unscopedPath)
|
||||
if err := os.MkdirAll(logRoot, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := stringid.GenerateNonCryptoID()
|
||||
a := &pluginAdapter{
|
||||
driverName: name,
|
||||
id: id,
|
||||
plugin: l,
|
||||
fifoPath: filepath.Join(logRoot, id),
|
||||
logInfo: logCtx,
|
||||
}
|
||||
|
||||
cap, err := a.plugin.Capabilities()
|
||||
if err == nil {
|
||||
a.capabilities = cap
|
||||
}
|
||||
|
||||
stream, err := openPluginStream(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.stream = stream
|
||||
a.enc = logdriver.NewLogEntryEncoder(a.stream)
|
||||
|
||||
if err := l.StartLogging(filepath.Join(unscopedPath, id), logCtx); err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating logger")
|
||||
}
|
||||
|
||||
if cap.ReadLogs {
|
||||
return &pluginAdapterWithRead{a}, nil
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/containerd/fifo"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func openPluginStream(a *pluginAdapter) (io.WriteCloser, error) {
|
||||
f, err := fifo.OpenFifo(context.Background(), a.fifoPath, unix.O_WRONLY|unix.O_CREAT|unix.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating i/o pipe for log plugin: %s", a.Name())
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// +build !linux,!freebsd
|
||||
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
func openPluginStream(a *pluginAdapter) (io.WriteCloser, error) {
|
||||
return nil, errors.New("log plugin not supported")
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type client interface {
|
||||
Call(string, interface{}, interface{}) error
|
||||
Stream(string, interface{}) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type logPluginProxy struct {
|
||||
client
|
||||
}
|
||||
|
||||
type logPluginProxyStartLoggingRequest struct {
|
||||
File string
|
||||
Info Info
|
||||
}
|
||||
|
||||
type logPluginProxyStartLoggingResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *logPluginProxy) StartLogging(file string, info Info) (err error) {
|
||||
var (
|
||||
req logPluginProxyStartLoggingRequest
|
||||
ret logPluginProxyStartLoggingResponse
|
||||
)
|
||||
|
||||
req.File = file
|
||||
req.Info = info
|
||||
if err = pp.Call("LogDriver.StartLogging", req, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type logPluginProxyStopLoggingRequest struct {
|
||||
File string
|
||||
}
|
||||
|
||||
type logPluginProxyStopLoggingResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *logPluginProxy) StopLogging(file string) (err error) {
|
||||
var (
|
||||
req logPluginProxyStopLoggingRequest
|
||||
ret logPluginProxyStopLoggingResponse
|
||||
)
|
||||
|
||||
req.File = file
|
||||
if err = pp.Call("LogDriver.StopLogging", req, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type logPluginProxyCapabilitiesResponse struct {
|
||||
Cap Capability
|
||||
Err string
|
||||
}
|
||||
|
||||
func (pp *logPluginProxy) Capabilities() (cap Capability, err error) {
|
||||
var (
|
||||
ret logPluginProxyCapabilitiesResponse
|
||||
)
|
||||
|
||||
if err = pp.Call("LogDriver.Capabilities", nil, &ret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cap = ret.Cap
|
||||
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type logPluginProxyReadLogsRequest struct {
|
||||
Info Info
|
||||
Config ReadConfig
|
||||
}
|
||||
|
||||
func (pp *logPluginProxy) ReadLogs(info Info, config ReadConfig) (stream io.ReadCloser, err error) {
|
||||
var (
|
||||
req logPluginProxyReadLogsRequest
|
||||
)
|
||||
|
||||
req.Info = info
|
||||
req.Config = config
|
||||
return pp.Stream("LogDriver.ReadLogs", req)
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
package logger // import "github.com/docker/docker/daemon/logger"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRingMaxSize = 1e6 // 1MB
|
||||
)
|
||||
|
||||
// RingLogger is a ring buffer that implements the Logger interface.
|
||||
// This is used when lossy logging is OK.
|
||||
type RingLogger struct {
|
||||
buffer *messageRing
|
||||
l Logger
|
||||
logInfo Info
|
||||
closeFlag int32
|
||||
}
|
||||
|
||||
type ringWithReader struct {
|
||||
*RingLogger
|
||||
}
|
||||
|
||||
func (r *ringWithReader) ReadLogs(cfg ReadConfig) *LogWatcher {
|
||||
reader, ok := r.l.(LogReader)
|
||||
if !ok {
|
||||
// something is wrong if we get here
|
||||
panic("expected log reader")
|
||||
}
|
||||
return reader.ReadLogs(cfg)
|
||||
}
|
||||
|
||||
func newRingLogger(driver Logger, logInfo Info, maxSize int64) *RingLogger {
|
||||
l := &RingLogger{
|
||||
buffer: newRing(maxSize),
|
||||
l: driver,
|
||||
logInfo: logInfo,
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// NewRingLogger creates a new Logger that is implemented as a RingBuffer wrapping
|
||||
// the passed in logger.
|
||||
func NewRingLogger(driver Logger, logInfo Info, maxSize int64) Logger {
|
||||
if maxSize < 0 {
|
||||
maxSize = defaultRingMaxSize
|
||||
}
|
||||
l := newRingLogger(driver, logInfo, maxSize)
|
||||
if _, ok := driver.(LogReader); ok {
|
||||
return &ringWithReader{l}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Log queues messages into the ring buffer
|
||||
func (r *RingLogger) Log(msg *Message) error {
|
||||
if r.closed() {
|
||||
return errClosed
|
||||
}
|
||||
return r.buffer.Enqueue(msg)
|
||||
}
|
||||
|
||||
// Name returns the name of the underlying logger
|
||||
func (r *RingLogger) Name() string {
|
||||
return r.l.Name()
|
||||
}
|
||||
|
||||
func (r *RingLogger) closed() bool {
|
||||
return atomic.LoadInt32(&r.closeFlag) == 1
|
||||
}
|
||||
|
||||
func (r *RingLogger) setClosed() {
|
||||
atomic.StoreInt32(&r.closeFlag, 1)
|
||||
}
|
||||
|
||||
// Close closes the logger
|
||||
func (r *RingLogger) Close() error {
|
||||
r.setClosed()
|
||||
r.buffer.Close()
|
||||
// empty out the queue
|
||||
var logErr bool
|
||||
for _, msg := range r.buffer.Drain() {
|
||||
if logErr {
|
||||
// some error logging a previous message, so re-insert to message pool
|
||||
// and assume log driver is hosed
|
||||
PutMessage(msg)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := r.l.Log(msg); err != nil {
|
||||
logrus.WithField("driver", r.l.Name()).
|
||||
WithField("container", r.logInfo.ContainerID).
|
||||
WithError(err).
|
||||
Errorf("Error writing log message")
|
||||
logErr = true
|
||||
}
|
||||
}
|
||||
return r.l.Close()
|
||||
}
|
||||
|
||||
// run consumes messages from the ring buffer and forwards them to the underling
|
||||
// logger.
|
||||
// This is run in a goroutine when the RingLogger is created
|
||||
func (r *RingLogger) run() {
|
||||
for {
|
||||
if r.closed() {
|
||||
return
|
||||
}
|
||||
msg, err := r.buffer.Dequeue()
|
||||
if err != nil {
|
||||
// buffer is closed
|
||||
return
|
||||
}
|
||||
if err := r.l.Log(msg); err != nil {
|
||||
logrus.WithField("driver", r.l.Name()).
|
||||
WithField("container", r.logInfo.ContainerID).
|
||||
WithError(err).
|
||||
Errorf("Error writing log message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type messageRing struct {
|
||||
mu sync.Mutex
|
||||
// signals callers of `Dequeue` to wake up either on `Close` or when a new `Message` is added
|
||||
wait *sync.Cond
|
||||
|
||||
sizeBytes int64 // current buffer size
|
||||
maxBytes int64 // max buffer size size
|
||||
queue []*Message
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newRing(maxBytes int64) *messageRing {
|
||||
queueSize := 1000
|
||||
if maxBytes == 0 || maxBytes == 1 {
|
||||
// With 0 or 1 max byte size, the maximum size of the queue would only ever be 1
|
||||
// message long.
|
||||
queueSize = 1
|
||||
}
|
||||
|
||||
r := &messageRing{queue: make([]*Message, 0, queueSize), maxBytes: maxBytes}
|
||||
r.wait = sync.NewCond(&r.mu)
|
||||
return r
|
||||
}
|
||||
|
||||
// Enqueue adds a message to the buffer queue
|
||||
// If the message is too big for the buffer it drops the oldest messages to make room
|
||||
// If there are no messages in the queue and the message is still too big, it adds the message anyway.
|
||||
func (r *messageRing) Enqueue(m *Message) error {
|
||||
mSize := int64(len(m.Line))
|
||||
|
||||
r.mu.Lock()
|
||||
if r.closed {
|
||||
r.mu.Unlock()
|
||||
return errClosed
|
||||
}
|
||||
if mSize+r.sizeBytes > r.maxBytes && len(r.queue) > 0 {
|
||||
r.wait.Signal()
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
r.queue = append(r.queue, m)
|
||||
r.sizeBytes += mSize
|
||||
r.wait.Signal()
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dequeue pulls a message off the queue
|
||||
// If there are no messages, it waits for one.
|
||||
// If the buffer is closed, it will return immediately.
|
||||
func (r *messageRing) Dequeue() (*Message, error) {
|
||||
r.mu.Lock()
|
||||
for len(r.queue) == 0 && !r.closed {
|
||||
r.wait.Wait()
|
||||
}
|
||||
|
||||
if r.closed {
|
||||
r.mu.Unlock()
|
||||
return nil, errClosed
|
||||
}
|
||||
|
||||
msg := r.queue[0]
|
||||
r.queue = r.queue[1:]
|
||||
r.sizeBytes -= int64(len(msg.Line))
|
||||
r.mu.Unlock()
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
var errClosed = errors.New("closed")
|
||||
|
||||
// Close closes the buffer ensuring no new messages can be added.
|
||||
// Any callers waiting to dequeue a message will be woken up.
|
||||
func (r *messageRing) Close() {
|
||||
r.mu.Lock()
|
||||
if r.closed {
|
||||
r.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
r.closed = true
|
||||
r.wait.Broadcast()
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
// Drain drains all messages from the queue.
|
||||
// This can be used after `Close()` to get any remaining messages that were in queue.
|
||||
func (r *messageRing) Drain() []*Message {
|
||||
r.mu.Lock()
|
||||
ls := make([]*Message, 0, len(r.queue))
|
||||
ls = append(ls, r.queue...)
|
||||
r.sizeBytes = 0
|
||||
r.queue = r.queue[:0]
|
||||
r.mu.Unlock()
|
||||
return ls
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package templates // import "github.com/docker/docker/daemon/logger/templates"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// basicFunctions are the set of initial
|
||||
// functions provided to every template.
|
||||
var basicFunctions = template.FuncMap{
|
||||
"json": func(v interface{}) string {
|
||||
buf := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.Encode(v)
|
||||
// Remove the trailing new line added by the encoder
|
||||
return strings.TrimSpace(buf.String())
|
||||
},
|
||||
"split": strings.Split,
|
||||
"join": strings.Join,
|
||||
"title": strings.Title,
|
||||
"lower": strings.ToLower,
|
||||
"upper": strings.ToUpper,
|
||||
"pad": padWithSpace,
|
||||
"truncate": truncateWithLength,
|
||||
}
|
||||
|
||||
// NewParse creates a new tagged template with the basic functions
|
||||
// and parses the given format.
|
||||
func NewParse(tag, format string) (*template.Template, error) {
|
||||
return template.New(tag).Funcs(basicFunctions).Parse(format)
|
||||
}
|
||||
|
||||
// padWithSpace adds whitespace to the input if the input is non-empty
|
||||
func padWithSpace(source string, prefix, suffix int) string {
|
||||
if source == "" {
|
||||
return source
|
||||
}
|
||||
return strings.Repeat(" ", prefix) + source + strings.Repeat(" ", suffix)
|
||||
}
|
||||
|
||||
// truncateWithLength truncates the source string up to the length provided by the input
|
||||
func truncateWithLength(source string, length int) string {
|
||||
if len(source) < length {
|
||||
return source
|
||||
}
|
||||
return source[:length]
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package network // import "github.com/docker/docker/daemon/network"
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
networktypes "github.com/docker/docker/api/types/network"
|
||||
clustertypes "github.com/docker/docker/daemon/cluster/provider"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Settings stores configuration details about the daemon network config
|
||||
// TODO Windows. Many of these fields can be factored out.,
|
||||
type Settings struct {
|
||||
Bridge string
|
||||
SandboxID string
|
||||
HairpinMode bool
|
||||
LinkLocalIPv6Address string
|
||||
LinkLocalIPv6PrefixLen int
|
||||
Networks map[string]*EndpointSettings
|
||||
Service *clustertypes.ServiceConfig
|
||||
Ports nat.PortMap
|
||||
SandboxKey string
|
||||
SecondaryIPAddresses []networktypes.Address
|
||||
SecondaryIPv6Addresses []networktypes.Address
|
||||
IsAnonymousEndpoint bool
|
||||
HasSwarmEndpoint bool
|
||||
}
|
||||
|
||||
// EndpointSettings is a package local wrapper for
|
||||
// networktypes.EndpointSettings which stores Endpoint state that
|
||||
// needs to be persisted to disk but not exposed in the api.
|
||||
type EndpointSettings struct {
|
||||
*networktypes.EndpointSettings
|
||||
IPAMOperational bool
|
||||
}
|
||||
|
||||
// AttachmentStore stores the load balancer IP address for a network id.
|
||||
type AttachmentStore struct {
|
||||
//key: networkd id
|
||||
//value: load balancer ip address
|
||||
networkToNodeLBIP map[string]net.IP
|
||||
}
|
||||
|
||||
// ResetAttachments clears any existing load balancer IP to network mapping and
|
||||
// sets the mapping to the given attachments.
|
||||
func (store *AttachmentStore) ResetAttachments(attachments map[string]string) error {
|
||||
store.ClearAttachments()
|
||||
for nid, nodeIP := range attachments {
|
||||
ip, _, err := net.ParseCIDR(nodeIP)
|
||||
if err != nil {
|
||||
store.networkToNodeLBIP = make(map[string]net.IP)
|
||||
return errors.Wrapf(err, "Failed to parse load balancer address %s", nodeIP)
|
||||
}
|
||||
store.networkToNodeLBIP[nid] = ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAttachments clears all the mappings of network to load balancer IP Address.
|
||||
func (store *AttachmentStore) ClearAttachments() {
|
||||
store.networkToNodeLBIP = make(map[string]net.IP)
|
||||
}
|
||||
|
||||
// GetIPForNetwork return the load balancer IP address for the given network.
|
||||
func (store *AttachmentStore) GetIPForNetwork(networkID string) (net.IP, bool) {
|
||||
ip, exists := store.networkToNodeLBIP[networkID]
|
||||
return ip, exists
|
||||
}
|
||||
Reference in New Issue
Block a user