Update vendor for go-runc and console
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
740005a298
commit
d47b9efd90
|
@ -14,8 +14,8 @@ clone git github.com/docker/go-units 5d2041e26a699eaca682e2ea41c8f891e1060444
|
|||
clone git github.com/godbus/dbus e2cf28118e66a6a63db46cf6088a35d2054d3bb0
|
||||
clone git github.com/golang/glog 23def4e6c14b4da8ac2ed8007337bc5eb5007998
|
||||
clone git github.com/golang/protobuf 8ee79997227bf9b34611aee7946ae64735e6fd93
|
||||
clone git github.com/opencontainers/runc 51371867a01c467f08af739783b8beafc154c4d7 https://github.com/docker/runc.git
|
||||
clone git github.com/opencontainers/runtime-spec 1c7c27d043c2a5e513a44084d2b10d77d1402b8c
|
||||
clone git github.com/opencontainers/runc b6b70e53451794e8333e9b602cc096b47a20bd0f
|
||||
clone git github.com/opencontainers/runtime-spec v1.0.0-rc5
|
||||
clone git github.com/rcrowley/go-metrics eeba7bd0dd01ace6e690fa833b3f22aaec29af43
|
||||
clone git github.com/satori/go.uuid f9ab0dce87d815821e221626b772e3475a0d2749
|
||||
clone git github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852
|
||||
|
@ -31,6 +31,9 @@ clone git github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
|
|||
clone git github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||
clone git github.com/go-check/check a625211d932a2a643d0d17352095f03fb7774663 https://github.com/cpuguy83/check.git
|
||||
|
||||
clone git github.com/crosbymichael/console 8ea0f623ee22736eec36b4ec87664b1d82cf9d15
|
||||
clone git github.com/crosbymichael/go-runc cfdb00928e8216dc80be58981368f56e8ba01f8e
|
||||
|
||||
# dependencies of docker/pkg/listeners
|
||||
clone git github.com/docker/go-connections v0.2.0
|
||||
clone git github.com/Microsoft/go-winio v0.3.2
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.7.x
|
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2017-infinity Michael Crosby. crosbymichael@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,44 @@
|
|||
# console
|
||||
|
||||
[![Build Status](https://travis-ci.org/crosbymichael/console.svg?branch=master)](https://travis-ci.org/crosbymichael/console)
|
||||
|
||||
Golang package for dealing with consoles. Light on deps and a simple API.
|
||||
|
||||
## Modifying the current process
|
||||
|
||||
```go
|
||||
current := console.Current()
|
||||
defer current.Reset()
|
||||
|
||||
if err := current.SetRaw(); err != nil {
|
||||
}
|
||||
ws, err := current.Size()
|
||||
current.Resize(ws)
|
||||
```
|
||||
|
||||
## License - MIT
|
||||
|
||||
Copyright (c) 2017-infinity Michael Crosby. crosbymichael@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,50 @@
|
|||
package console
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ErrNotAConsole = errors.New("provided file is not a console")
|
||||
|
||||
type Console interface {
|
||||
io.Reader
|
||||
io.Writer
|
||||
io.Closer
|
||||
|
||||
// Resize resizes the console to the provided window size
|
||||
Resize(WinSize) error
|
||||
// ResizeFrom resizes the calling console to the size of the
|
||||
// provided console
|
||||
ResizeFrom(Console) error
|
||||
// SetRaw sets the console in raw mode
|
||||
SetRaw() error
|
||||
// Reset restores the console to its orignal state
|
||||
Reset() error
|
||||
// Size returns the window size of the console
|
||||
Size() (WinSize, error)
|
||||
}
|
||||
|
||||
// WinSize specifies the window size of the console
|
||||
type WinSize struct {
|
||||
// Width of the console
|
||||
Width uint16
|
||||
// Height of the console
|
||||
Height uint16
|
||||
x uint16
|
||||
y uint16
|
||||
}
|
||||
|
||||
// Current returns the current processes console
|
||||
func Current() Console {
|
||||
return newMaster(os.Stdin)
|
||||
}
|
||||
|
||||
// ConsoleFromFile returns a console using the provided file
|
||||
func ConsoleFromFile(f *os.File) (Console, error) {
|
||||
if err := checkConsole(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newMaster(f), nil
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package console
|
||||
|
||||
// #include <termios.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewPty creates a new pty pair
|
||||
// The master is returned as the first console and a string
|
||||
// with the path to the pty slave is returned as the second
|
||||
func NewPty() (Console, string, error) {
|
||||
f, err := os.OpenFile("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err := saneTerminal(f); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
slave, err := ptsname(f)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err := unlockpt(f); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return &master{
|
||||
f: f,
|
||||
}, slave, nil
|
||||
}
|
||||
|
||||
type master struct {
|
||||
f *os.File
|
||||
termios *unix.Termios
|
||||
}
|
||||
|
||||
func (m *master) Read(b []byte) (int, error) {
|
||||
return m.f.Read(b)
|
||||
}
|
||||
|
||||
func (m *master) Write(b []byte) (int, error) {
|
||||
return m.f.Write(b)
|
||||
}
|
||||
|
||||
func (m *master) Close() error {
|
||||
return m.f.Close()
|
||||
}
|
||||
|
||||
func (m *master) Resize(ws WinSize) error {
|
||||
return ioctl(
|
||||
m.f.Fd(),
|
||||
uintptr(unix.TIOCSWINSZ),
|
||||
uintptr(unsafe.Pointer(&ws)),
|
||||
)
|
||||
}
|
||||
|
||||
func (m *master) ResizeFrom(c Console) error {
|
||||
ws, err := c.Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.Resize(ws)
|
||||
}
|
||||
|
||||
func (m *master) Reset() error {
|
||||
if m.termios == nil {
|
||||
return nil
|
||||
}
|
||||
return tcset(m.f.Fd(), m.termios)
|
||||
}
|
||||
|
||||
func (m *master) SetRaw() error {
|
||||
m.termios = &unix.Termios{}
|
||||
if err := tcget(m.f.Fd(), m.termios); err != nil {
|
||||
return err
|
||||
}
|
||||
rawState := *m.termios
|
||||
C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&rawState)))
|
||||
rawState.Oflag = rawState.Oflag | C.OPOST
|
||||
return tcset(m.f.Fd(), &rawState)
|
||||
}
|
||||
|
||||
func (m *master) Size() (WinSize, error) {
|
||||
var ws WinSize
|
||||
if err := ioctl(
|
||||
m.f.Fd(),
|
||||
uintptr(unix.TIOCGWINSZ),
|
||||
uintptr(unsafe.Pointer(&ws)),
|
||||
); err != nil {
|
||||
return ws, err
|
||||
}
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
// checkConsole checks if the provided file is a console
|
||||
func checkConsole(f *os.File) error {
|
||||
var termios unix.Termios
|
||||
if tcget(f.Fd(), &termios) != nil {
|
||||
return ErrNotAConsole
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMaster(f *os.File) Console {
|
||||
return &master{
|
||||
f: f,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Azure/go-ansiterm/winterm"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
|
||||
enableVirtualTerminalInput = 0x0200
|
||||
enableVirtualTerminalProcessing = 0x0004
|
||||
disableNewlineAutoReturn = 0x0008
|
||||
)
|
||||
|
||||
var (
|
||||
vtInputSupported bool
|
||||
ErrNotImplemented = errors.New("not implemented")
|
||||
)
|
||||
|
||||
func (m *master) initStdios() {
|
||||
m.in = os.Stdin.Fd()
|
||||
if mode, err := winterm.GetConsoleMode(m.in); err == nil {
|
||||
m.inMode = mode
|
||||
// Validate that enableVirtualTerminalInput is supported, but do not set it.
|
||||
if err = winterm.SetConsoleMode(m.in, mode|enableVirtualTerminalInput); err == nil {
|
||||
vtInputSupported = true
|
||||
}
|
||||
// Unconditionally set the console mode back even on failure because SetConsoleMode
|
||||
// remembers invalid bits on input handles.
|
||||
winterm.SetConsoleMode(m.in, mode)
|
||||
} else {
|
||||
fmt.Printf("failed to get console mode for stdin: %v\n", err)
|
||||
}
|
||||
|
||||
m.out = os.Stdout.Fd()
|
||||
if mode, err := winterm.GetConsoleMode(m.out); err == nil {
|
||||
m.outMode = mode
|
||||
if err := winterm.SetConsoleMode(m.out, mode|enableVirtualTerminalProcessing); err == nil {
|
||||
m.outMode |= enableVirtualTerminalProcessing
|
||||
} else {
|
||||
winterm.SetConsoleMode(m.out, m.outMode)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("failed to get console mode for stdout: %v\n", err)
|
||||
}
|
||||
|
||||
m.err = os.Stderr.Fd()
|
||||
if mode, err := winterm.GetConsoleMode(m.err); err == nil {
|
||||
m.errMode = mode
|
||||
if err := winterm.SetConsoleMode(m.err, mode|enableVirtualTerminalProcessing); err == nil {
|
||||
m.errMode |= enableVirtualTerminalProcessing
|
||||
} else {
|
||||
winterm.SetConsoleMode(m.err, m.errMode)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("failed to get console mode for stderr: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
type master struct {
|
||||
in uintptr
|
||||
inMode uint32
|
||||
|
||||
out uintptr
|
||||
outMode uint32
|
||||
|
||||
err uintptr
|
||||
errMode uint32
|
||||
}
|
||||
|
||||
func (m *master) SetRaw() error {
|
||||
if err := makeInputRaw(m.in, m.inMode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set StdOut and StdErr to raw mode, we ignore failures since
|
||||
// disableNewlineAutoReturn might not be supported on this version of
|
||||
// Windows.
|
||||
|
||||
winterm.SetConsoleMode(m.out, m.outMode|disableNewlineAutoReturn)
|
||||
|
||||
winterm.SetConsoleMode(m.err, m.errMode|disableNewlineAutoReturn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *master) Reset() error {
|
||||
for _, s := range []struct {
|
||||
fd uintptr
|
||||
mode uint32
|
||||
}{
|
||||
{m.in, m.inMode},
|
||||
{m.out, m.outMode},
|
||||
{m.err, m.errMode},
|
||||
} {
|
||||
if err := winterm.SetConsoleMode(s.fd, s.mode); err != nil {
|
||||
return errors.Wrap(err, "unable to restore console mode")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *master) Size() (WinSize, error) {
|
||||
info, err := winterm.GetConsoleScreenBufferInfo(m.out)
|
||||
if err != nil {
|
||||
return WinSize{}, errors.Wrap(err, "unable to get console info")
|
||||
}
|
||||
|
||||
winsize := WinSize{
|
||||
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
||||
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
||||
}
|
||||
|
||||
return winsize, nil
|
||||
}
|
||||
|
||||
func (m *master) Resize(ws WinSize) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
func (m *master) ResizeFrom(c Console) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
func (m *master) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *master) Read(b []byte) (int, error) {
|
||||
panic("not implemented on windows")
|
||||
}
|
||||
|
||||
func (m *master) Write(b []byte) (int, error) {
|
||||
panic("not implemented on windows")
|
||||
}
|
||||
|
||||
// makeInputRaw puts the terminal (Windows Console) connected to the given
|
||||
// file descriptor into raw mode
|
||||
func makeInputRaw(fd uintptr, mode uint32) error {
|
||||
// See
|
||||
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||
|
||||
// Disable these modes
|
||||
mode &^= winterm.ENABLE_ECHO_INPUT
|
||||
mode &^= winterm.ENABLE_LINE_INPUT
|
||||
mode &^= winterm.ENABLE_MOUSE_INPUT
|
||||
mode &^= winterm.ENABLE_WINDOW_INPUT
|
||||
mode &^= winterm.ENABLE_PROCESSED_INPUT
|
||||
|
||||
// Enable these modes
|
||||
mode |= winterm.ENABLE_EXTENDED_FLAGS
|
||||
mode |= winterm.ENABLE_INSERT_MODE
|
||||
mode |= winterm.ENABLE_QUICK_EDIT_MODE
|
||||
|
||||
if vtInputSupported {
|
||||
mode |= enableVirtualTerminalInput
|
||||
}
|
||||
|
||||
if err := winterm.SetConsoleMode(fd, mode); err != nil {
|
||||
return errors.Wrap(err, "unable to set console to raw mode")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkConsole(f *os.File) error {
|
||||
if _, err := winterm.GetConsoleMode(f.Fd()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMaster(f *os.File) Console {
|
||||
if f != os.Stdin && f != os.Stdout && f != os.Stderr {
|
||||
panic("creating a console from a file is not supported on windows")
|
||||
}
|
||||
|
||||
m := &master{}
|
||||
m.initStdios()
|
||||
|
||||
return m
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func tcget(fd uintptr, p *unix.Termios) error {
|
||||
return ioctl(fd, unix.TCGETS, uintptr(unsafe.Pointer(p)))
|
||||
}
|
||||
|
||||
func tcset(fd uintptr, p *unix.Termios) error {
|
||||
return ioctl(fd, unix.TCSETS, uintptr(unsafe.Pointer(p)))
|
||||
}
|
||||
|
||||
func ioctl(fd, flag, data uintptr) error {
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||
// unlockpt should be called before opening the slave side of a pty.
|
||||
func unlockpt(f *os.File) error {
|
||||
var u int32
|
||||
return ioctl(f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
|
||||
}
|
||||
|
||||
// ptsname retrieves the name of the first available pts for the given master.
|
||||
func ptsname(f *os.File) (string, error) {
|
||||
var n int32
|
||||
if err := ioctl(f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||
}
|
||||
|
||||
func saneTerminal(f *os.File) error {
|
||||
// Go doesn't have a wrapper for any of the termios ioctls.
|
||||
var termios unix.Termios
|
||||
if err := tcget(f.Fd(), &termios); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set -onlcr so we don't have to deal with \r.
|
||||
termios.Oflag &^= unix.ONLCR
|
||||
return tcset(f.Fd(), &termios)
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// +build !linux
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func tcget(fd uintptr, p *unix.Termios) error {
|
||||
return ioctl(fd, unix.TIOCGETA, uintptr(unsafe.Pointer(p)))
|
||||
}
|
||||
|
||||
func tcset(fd uintptr, p *unix.Termios) error {
|
||||
return ioctl(fd, unix.TIOCSETA, uintptr(unsafe.Pointer(p)))
|
||||
}
|
||||
|
||||
func ioctl(fd, flag, data uintptr) error {
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||
// unlockpt should be called before opening the slave side of a pty.
|
||||
func unlockpt(f *os.File) error {
|
||||
var u int32
|
||||
return ioctl(f.Fd(), unix.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
|
||||
}
|
||||
|
||||
// ptsname retrieves the name of the first available pts for the given master.
|
||||
func ptsname(f *os.File) (string, error) {
|
||||
var n int32
|
||||
if err := ioctl(f.Fd(), unix.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n))); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||
}
|
||||
|
||||
func saneTerminal(f *os.File) error {
|
||||
// Go doesn't have a wrapper for any of the termios ioctls.
|
||||
var termios unix.Termios
|
||||
if err := tcget(f.Fd(), &termios); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set -onlcr so we don't have to deal with \r.
|
||||
termios.Oflag &^= unix.ONLCR
|
||||
return tcset(f.Fd(), &termios)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.7.x
|
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2016 Michael Crosby. crosbymichael@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,42 @@
|
|||
# go-runc
|
||||
|
||||
[![Build Status](https://travis-ci.org/crosbymichael/go-runc.svg?branch=master)](https://travis-ci.org/crosbymichael/go-runc)
|
||||
|
||||
|
||||
This is a package for consuming the [runc](https://github.com/opencontainers/runc) binary in your Go applications.
|
||||
It tries to expose all the settings and features of the runc CLI. If there is something missing then add it, its opensource!
|
||||
|
||||
This needs runc @ [a9610f2c0](https://github.com/opencontainers/runc/commit/a9610f2c0237d2636d05a031ec8659a70e75ffeb)
|
||||
or greater.
|
||||
|
||||
## Docs
|
||||
|
||||
Docs can be found at [godoc.org](https://godoc.org/github.com/crosbymichael/go-runc).
|
||||
|
||||
|
||||
## LICENSE - MIT
|
||||
|
||||
Copyright (c) 2016 Michael Crosby. crosbymichael@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,67 @@
|
|||
// +build cgo
|
||||
|
||||
package runc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/crosbymichael/console"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
)
|
||||
|
||||
// NewConsoleSocket creates a new unix socket at the provided path to accept a
|
||||
// pty master created by runc for use by the container
|
||||
func NewConsoleSocket(path string) (*Socket, error) {
|
||||
abs, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := net.Listen("unix", abs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Socket{
|
||||
l: l,
|
||||
path: abs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Socket is a unix socket that accepts the pty master created by runc
|
||||
type Socket struct {
|
||||
path string
|
||||
l net.Listener
|
||||
}
|
||||
|
||||
// Path returns the path to the unix socket on disk
|
||||
func (c *Socket) Path() string {
|
||||
return c.path
|
||||
}
|
||||
|
||||
// ReceiveMaster blocks until the socket receives the pty master
|
||||
func (c *Socket) ReceiveMaster() (console.Console, error) {
|
||||
conn, err := c.l.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
unix, ok := conn.(*net.UnixConn)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("received connection which was not a unix socket")
|
||||
}
|
||||
sock, err := unix.File()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := utils.RecvFd(sock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return console.ConsoleFromFile(f)
|
||||
}
|
||||
|
||||
// Close closes the unix socket
|
||||
func (c *Socket) Close() error {
|
||||
return c.l.Close()
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package runc
|
||||
|
||||
import "time"
|
||||
|
||||
// Container hold information for a runc container
|
||||
type Container struct {
|
||||
ID string `json:"id"`
|
||||
Pid int `json:"pid"`
|
||||
Status string `json:"status"`
|
||||
Bundle string `json:"bundle"`
|
||||
Rootfs string `json:"rootfs"`
|
||||
Created time.Time `json:"created"`
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package runc
|
||||
|
||||
type Event struct {
|
||||
// Type are the event type generated by runc
|
||||
// If the type is "error" then check the Err field on the event for
|
||||
// the actual error
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
Stats *Stats `json:"data,omitempty"`
|
||||
// Err has a read error if we were unable to decode the event from runc
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
Cpu Cpu `json:"cpu"`
|
||||
Memory Memory `json:"memory"`
|
||||
Pids Pids `json:"pids"`
|
||||
Blkio Blkio `json:"blkio"`
|
||||
Hugetlb map[string]Hugetlb `json:"hugetlb"`
|
||||
}
|
||||
|
||||
type Hugetlb struct {
|
||||
Usage uint64 `json:"usage,omitempty"`
|
||||
Max uint64 `json:"max,omitempty"`
|
||||
Failcnt uint64 `json:"failcnt"`
|
||||
}
|
||||
|
||||
type BlkioEntry struct {
|
||||
Major uint64 `json:"major,omitempty"`
|
||||
Minor uint64 `json:"minor,omitempty"`
|
||||
Op string `json:"op,omitempty"`
|
||||
Value uint64 `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Blkio struct {
|
||||
IoServiceBytesRecursive []BlkioEntry `json:"ioServiceBytesRecursive,omitempty"`
|
||||
IoServicedRecursive []BlkioEntry `json:"ioServicedRecursive,omitempty"`
|
||||
IoQueuedRecursive []BlkioEntry `json:"ioQueueRecursive,omitempty"`
|
||||
IoServiceTimeRecursive []BlkioEntry `json:"ioServiceTimeRecursive,omitempty"`
|
||||
IoWaitTimeRecursive []BlkioEntry `json:"ioWaitTimeRecursive,omitempty"`
|
||||
IoMergedRecursive []BlkioEntry `json:"ioMergedRecursive,omitempty"`
|
||||
IoTimeRecursive []BlkioEntry `json:"ioTimeRecursive,omitempty"`
|
||||
SectorsRecursive []BlkioEntry `json:"sectorsRecursive,omitempty"`
|
||||
}
|
||||
|
||||
type Pids struct {
|
||||
Current uint64 `json:"current,omitempty"`
|
||||
Limit uint64 `json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
type Throttling struct {
|
||||
Periods uint64 `json:"periods,omitempty"`
|
||||
ThrottledPeriods uint64 `json:"throttledPeriods,omitempty"`
|
||||
ThrottledTime uint64 `json:"throttledTime,omitempty"`
|
||||
}
|
||||
|
||||
type CpuUsage struct {
|
||||
// Units: nanoseconds.
|
||||
Total uint64 `json:"total,omitempty"`
|
||||
Percpu []uint64 `json:"percpu,omitempty"`
|
||||
Kernel uint64 `json:"kernel"`
|
||||
User uint64 `json:"user"`
|
||||
}
|
||||
|
||||
type Cpu struct {
|
||||
Usage CpuUsage `json:"usage,omitempty"`
|
||||
Throttling Throttling `json:"throttling,omitempty"`
|
||||
}
|
||||
|
||||
type MemoryEntry struct {
|
||||
Limit uint64 `json:"limit"`
|
||||
Usage uint64 `json:"usage,omitempty"`
|
||||
Max uint64 `json:"max,omitempty"`
|
||||
Failcnt uint64 `json:"failcnt"`
|
||||
}
|
||||
|
||||
type Memory struct {
|
||||
Cache uint64 `json:"cache,omitempty"`
|
||||
Usage MemoryEntry `json:"usage,omitempty"`
|
||||
Swap MemoryEntry `json:"swap,omitempty"`
|
||||
Kernel MemoryEntry `json:"kernel,omitempty"`
|
||||
KernelTCP MemoryEntry `json:"kernelTCP,omitempty"`
|
||||
Raw map[string]uint64 `json:"raw,omitempty"`
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package runc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type IO interface {
|
||||
io.Closer
|
||||
Stdin() io.WriteCloser
|
||||
Stdout() io.ReadCloser
|
||||
Stderr() io.ReadCloser
|
||||
Set(*exec.Cmd)
|
||||
}
|
||||
|
||||
type StartCloser interface {
|
||||
CloseAfterStart() error
|
||||
}
|
||||
|
||||
// NewPipeIO creates pipe pairs to be used with runc
|
||||
func NewPipeIO(uid, gid int) (i IO, err error) {
|
||||
var pipes []*pipe
|
||||
// cleanup in case of an error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
for _, p := range pipes {
|
||||
p.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
stdin, err := newPipe(uid, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pipes = append(pipes, stdin)
|
||||
|
||||
stdout, err := newPipe(uid, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pipes = append(pipes, stdout)
|
||||
|
||||
stderr, err := newPipe(uid, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pipes = append(pipes, stderr)
|
||||
|
||||
return &pipeIO{
|
||||
in: stdin,
|
||||
out: stdout,
|
||||
err: stderr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newPipe(uid, gid int) (*pipe, error) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := unix.Fchown(int(r.Fd()), uid, gid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := unix.Fchown(int(w.Fd()), uid, gid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pipe{
|
||||
r: r,
|
||||
w: w,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type pipe struct {
|
||||
r *os.File
|
||||
w *os.File
|
||||
}
|
||||
|
||||
func (p *pipe) Close() error {
|
||||
err := p.r.Close()
|
||||
if werr := p.w.Close(); err == nil {
|
||||
err = werr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type pipeIO struct {
|
||||
in *pipe
|
||||
out *pipe
|
||||
err *pipe
|
||||
}
|
||||
|
||||
func (i *pipeIO) Stdin() io.WriteCloser {
|
||||
return i.in.w
|
||||
}
|
||||
|
||||
func (i *pipeIO) Stdout() io.ReadCloser {
|
||||
return i.out.r
|
||||
}
|
||||
|
||||
func (i *pipeIO) Stderr() io.ReadCloser {
|
||||
return i.err.r
|
||||
}
|
||||
|
||||
func (i *pipeIO) Close() error {
|
||||
var err error
|
||||
for _, v := range []*pipe{
|
||||
i.in,
|
||||
i.out,
|
||||
i.err,
|
||||
} {
|
||||
if cerr := v.Close(); err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *pipeIO) CloseAfterStart() error {
|
||||
for _, f := range []*os.File{
|
||||
i.out.w,
|
||||
i.err.w,
|
||||
} {
|
||||
f.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set sets the io to the exec.Cmd
|
||||
func (i *pipeIO) Set(cmd *exec.Cmd) {
|
||||
cmd.Stdin = i.in.r
|
||||
cmd.Stdout = i.out.w
|
||||
cmd.Stderr = i.err.w
|
||||
}
|
||||
|
||||
func NewSTDIO() (IO, error) {
|
||||
return &stdio{}, nil
|
||||
}
|
||||
|
||||
type stdio struct {
|
||||
}
|
||||
|
||||
func (s *stdio) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stdio) Set(cmd *exec.Cmd) {
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
func (s *stdio) Stdin() io.WriteCloser {
|
||||
return os.Stdin
|
||||
}
|
||||
|
||||
func (s *stdio) Stdout() io.ReadCloser {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
func (s *stdio) Stderr() io.ReadCloser {
|
||||
return os.Stderr
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package runc
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var Monitor ProcessMonitor = &defaultMonitor{}
|
||||
|
||||
// ProcessMonitor is an interface for process monitoring
|
||||
//
|
||||
// It allows daemons using go-runc to have a SIGCHLD handler
|
||||
// to handle exits without introducing races between the handler
|
||||
// and go's exec.Cmd
|
||||
// These methods should match the methods exposed by exec.Cmd to provide
|
||||
// a consistent experience for the caller
|
||||
type ProcessMonitor interface {
|
||||
Output(*exec.Cmd) ([]byte, error)
|
||||
CombinedOutput(*exec.Cmd) ([]byte, error)
|
||||
Run(*exec.Cmd) error
|
||||
Start(*exec.Cmd) error
|
||||
Wait(*exec.Cmd) (int, error)
|
||||
}
|
||||
|
||||
type defaultMonitor struct {
|
||||
}
|
||||
|
||||
func (m *defaultMonitor) Output(c *exec.Cmd) ([]byte, error) {
|
||||
return c.Output()
|
||||
}
|
||||
|
||||
func (m *defaultMonitor) CombinedOutput(c *exec.Cmd) ([]byte, error) {
|
||||
return c.CombinedOutput()
|
||||
}
|
||||
|
||||
func (m *defaultMonitor) Run(c *exec.Cmd) error {
|
||||
return c.Run()
|
||||
}
|
||||
|
||||
func (m *defaultMonitor) Start(c *exec.Cmd) error {
|
||||
return c.Start()
|
||||
}
|
||||
|
||||
func (m *defaultMonitor) Wait(c *exec.Cmd) (int, error) {
|
||||
status, err := c.Process.Wait()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return status.Sys().(syscall.WaitStatus).ExitStatus(), nil
|
||||
}
|
|
@ -0,0 +1,553 @@
|
|||
package runc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// Format is the type of log formatting options avaliable
|
||||
type Format string
|
||||
|
||||
const (
|
||||
none Format = ""
|
||||
JSON Format = "json"
|
||||
Text Format = "text"
|
||||
// DefaultCommand is the default command for Runc
|
||||
DefaultCommand = "runc"
|
||||
)
|
||||
|
||||
// Runc is the client to the runc cli
|
||||
type Runc struct {
|
||||
//If command is empty, DefaultCommand is used
|
||||
Command string
|
||||
Root string
|
||||
Debug bool
|
||||
Log string
|
||||
LogFormat Format
|
||||
PdeathSignal syscall.Signal
|
||||
Criu string
|
||||
}
|
||||
|
||||
// List returns all containers created inside the provided runc root directory
|
||||
func (r *Runc) List(context context.Context) ([]*Container, error) {
|
||||
data, err := Monitor.Output(r.command(context, "list", "--format=json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []*Container
|
||||
if err := json.Unmarshal(data, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// State returns the state for the container provided by id
|
||||
func (r *Runc) State(context context.Context, id string) (*Container, error) {
|
||||
data, err := Monitor.CombinedOutput(r.command(context, "state", id))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", err, data)
|
||||
}
|
||||
var c Container
|
||||
if err := json.Unmarshal(data, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
type ConsoleSocket interface {
|
||||
Path() string
|
||||
}
|
||||
|
||||
type CreateOpts struct {
|
||||
IO
|
||||
// PidFile is a path to where a pid file should be created
|
||||
PidFile string
|
||||
ConsoleSocket ConsoleSocket
|
||||
Detach bool
|
||||
NoPivot bool
|
||||
NoNewKeyring bool
|
||||
ExtraFiles []*os.File
|
||||
}
|
||||
|
||||
func (o *CreateOpts) args() (out []string, err error) {
|
||||
if o.PidFile != "" {
|
||||
abs, err := filepath.Abs(o.PidFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, "--pid-file", abs)
|
||||
}
|
||||
if o.ConsoleSocket != nil {
|
||||
out = append(out, "--console-socket", o.ConsoleSocket.Path())
|
||||
}
|
||||
if o.NoPivot {
|
||||
out = append(out, "--no-pivot")
|
||||
}
|
||||
if o.NoNewKeyring {
|
||||
out = append(out, "--no-new-keyring")
|
||||
}
|
||||
if o.Detach {
|
||||
out = append(out, "--detach")
|
||||
}
|
||||
if o.ExtraFiles != nil {
|
||||
out = append(out, "--preserve-fds", strconv.Itoa(len(o.ExtraFiles)))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Create creates a new container and returns its pid if it was created successfully
|
||||
func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
|
||||
args := []string{"create", "--bundle", bundle}
|
||||
if opts != nil {
|
||||
oargs, err := opts.args()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = append(args, oargs...)
|
||||
}
|
||||
cmd := r.command(context, append(args, id)...)
|
||||
if opts != nil && opts.IO != nil {
|
||||
opts.Set(cmd)
|
||||
}
|
||||
cmd.ExtraFiles = opts.ExtraFiles
|
||||
|
||||
if cmd.Stdout == nil && cmd.Stderr == nil {
|
||||
data, err := Monitor.CombinedOutput(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", err, data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := Monitor.Start(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts != nil && opts.IO != nil {
|
||||
if c, ok := opts.IO.(StartCloser); ok {
|
||||
if err := c.CloseAfterStart(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err := Monitor.Wait(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Start will start an already created container
|
||||
func (r *Runc) Start(context context.Context, id string) error {
|
||||
return r.runOrError(r.command(context, "start", id))
|
||||
}
|
||||
|
||||
type ExecOpts struct {
|
||||
IO
|
||||
PidFile string
|
||||
ConsoleSocket ConsoleSocket
|
||||
Detach bool
|
||||
}
|
||||
|
||||
func (o *ExecOpts) args() (out []string, err error) {
|
||||
if o.ConsoleSocket != nil {
|
||||
out = append(out, "--console-socket", o.ConsoleSocket.Path())
|
||||
}
|
||||
if o.Detach {
|
||||
out = append(out, "--detach")
|
||||
}
|
||||
if o.PidFile != "" {
|
||||
abs, err := filepath.Abs(o.PidFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, "--pid-file", abs)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Exec executres and additional process inside the container based on a full
|
||||
// OCI Process specification
|
||||
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
|
||||
f, err := ioutil.TempFile("", "runc-process")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
err = json.NewEncoder(f).Encode(spec)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := []string{"exec", "--process", f.Name()}
|
||||
if opts != nil {
|
||||
oargs, err := opts.args()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = append(args, oargs...)
|
||||
}
|
||||
cmd := r.command(context, append(args, id)...)
|
||||
if opts != nil && opts.IO != nil {
|
||||
opts.Set(cmd)
|
||||
}
|
||||
if cmd.Stdout == nil && cmd.Stderr == nil {
|
||||
data, err := Monitor.CombinedOutput(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", err, data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := Monitor.Start(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts != nil && opts.IO != nil {
|
||||
if c, ok := opts.IO.(StartCloser); ok {
|
||||
if err := c.CloseAfterStart(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = Monitor.Wait(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Run runs the create, start, delete lifecycle of the container
|
||||
// and returns its exit status after it has exited
|
||||
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
|
||||
args := []string{"run", "--bundle", bundle}
|
||||
if opts != nil {
|
||||
oargs, err := opts.args()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
args = append(args, oargs...)
|
||||
}
|
||||
cmd := r.command(context, append(args, id)...)
|
||||
if opts != nil {
|
||||
opts.Set(cmd)
|
||||
}
|
||||
if err := Monitor.Start(cmd); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return Monitor.Wait(cmd)
|
||||
}
|
||||
|
||||
// Delete deletes the container
|
||||
func (r *Runc) Delete(context context.Context, id string) error {
|
||||
return r.runOrError(r.command(context, "delete", id))
|
||||
}
|
||||
|
||||
// KillOpts specifies options for killing a container and its processes
|
||||
type KillOpts struct {
|
||||
All bool
|
||||
}
|
||||
|
||||
func (o *KillOpts) args() (out []string) {
|
||||
if o.All {
|
||||
out = append(out, "--all")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Kill sends the specified signal to the container
|
||||
func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
|
||||
args := []string{
|
||||
"kill",
|
||||
}
|
||||
if opts != nil {
|
||||
args = append(args, opts.args()...)
|
||||
}
|
||||
return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
|
||||
}
|
||||
|
||||
// Stats return the stats for a container like cpu, memory, and io
|
||||
func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
|
||||
cmd := r.command(context, "events", "--stats", id)
|
||||
rd, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
rd.Close()
|
||||
Monitor.Wait(cmd)
|
||||
}()
|
||||
if err := Monitor.Start(cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var e Event
|
||||
if err := json.NewDecoder(rd).Decode(&e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.Stats, nil
|
||||
}
|
||||
|
||||
// Events returns an event stream from runc for a container with stats and OOM notifications
|
||||
func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
|
||||
cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
|
||||
rd, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := Monitor.Start(cmd); err != nil {
|
||||
rd.Close()
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
dec = json.NewDecoder(rd)
|
||||
c = make(chan *Event, 128)
|
||||
)
|
||||
go func() {
|
||||
defer func() {
|
||||
close(c)
|
||||
rd.Close()
|
||||
Monitor.Wait(cmd)
|
||||
}()
|
||||
for {
|
||||
var e Event
|
||||
if err := dec.Decode(&e); err != nil {
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
e = Event{
|
||||
Type: "error",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
c <- &e
|
||||
}
|
||||
}()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Pause the container with the provided id
|
||||
func (r *Runc) Pause(context context.Context, id string) error {
|
||||
return r.runOrError(r.command(context, "pause", id))
|
||||
}
|
||||
|
||||
// Resume the container with the provided id
|
||||
func (r *Runc) Resume(context context.Context, id string) error {
|
||||
return r.runOrError(r.command(context, "resume", id))
|
||||
}
|
||||
|
||||
// Ps lists all the processes inside the container returning their pids
|
||||
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
|
||||
data, err := Monitor.CombinedOutput(r.command(context, "ps", "--format", "json", id))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", err, data)
|
||||
}
|
||||
var pids []int
|
||||
if err := json.Unmarshal(data, &pids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pids, nil
|
||||
}
|
||||
|
||||
type CheckpointOpts struct {
|
||||
// ImagePath is the path for saving the criu image file
|
||||
ImagePath string
|
||||
// WorkDir is the working directory for criu
|
||||
WorkDir string
|
||||
// ParentPath is the path for previous image files from a pre-dump
|
||||
ParentPath string
|
||||
// AllowOpenTCP allows open tcp connections to be checkpointed
|
||||
AllowOpenTCP bool
|
||||
// AllowExternalUnixSockets allows external unix sockets to be checkpointed
|
||||
AllowExternalUnixSockets bool
|
||||
// AllowTerminal allows the terminal(pty) to be checkpointed with a container
|
||||
AllowTerminal bool
|
||||
// CriuPageServer is the address:port for the criu page server
|
||||
CriuPageServer string
|
||||
// FileLocks handle file locks held by the container
|
||||
FileLocks bool
|
||||
// Cgroups is the cgroup mode for how to handle the checkpoint of a container's cgroups
|
||||
Cgroups CgroupMode
|
||||
// EmptyNamespaces creates a namespace for the container but does not save its properties
|
||||
// Provide the namespaces you wish to be checkpointed without their settings on restore
|
||||
EmptyNamespaces []string
|
||||
}
|
||||
|
||||
type CgroupMode string
|
||||
|
||||
const (
|
||||
Soft CgroupMode = "soft"
|
||||
Full CgroupMode = "full"
|
||||
Strict CgroupMode = "strict"
|
||||
)
|
||||
|
||||
func (o *CheckpointOpts) args() (out []string) {
|
||||
if o.ImagePath != "" {
|
||||
out = append(out, "--image-path", o.ImagePath)
|
||||
}
|
||||
if o.WorkDir != "" {
|
||||
out = append(out, "--work-path", o.WorkDir)
|
||||
}
|
||||
if o.ParentPath != "" {
|
||||
out = append(out, "--parent-path", o.ParentPath)
|
||||
}
|
||||
if o.AllowOpenTCP {
|
||||
out = append(out, "--tcp-established")
|
||||
}
|
||||
if o.AllowExternalUnixSockets {
|
||||
out = append(out, "--ext-unix-sk")
|
||||
}
|
||||
if o.AllowTerminal {
|
||||
out = append(out, "--shell-job")
|
||||
}
|
||||
if o.CriuPageServer != "" {
|
||||
out = append(out, "--page-server", o.CriuPageServer)
|
||||
}
|
||||
if o.FileLocks {
|
||||
out = append(out, "--file-locks")
|
||||
}
|
||||
if string(o.Cgroups) != "" {
|
||||
out = append(out, "--manage-cgroups-mode", string(o.Cgroups))
|
||||
}
|
||||
for _, ns := range o.EmptyNamespaces {
|
||||
out = append(out, "--empty-ns", ns)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type CheckpointAction func([]string) []string
|
||||
|
||||
// LeaveRunning keeps the container running after the checkpoint has been completed
|
||||
func LeaveRunning(args []string) []string {
|
||||
return append(args, "--leave-running")
|
||||
}
|
||||
|
||||
// PreDump allows a pre-dump of the checkpoint to be made and completed later
|
||||
func PreDump(args []string) []string {
|
||||
return append(args, "--pre-dump")
|
||||
}
|
||||
|
||||
// Checkpoint allows you to checkpoint a container using criu
|
||||
func (r *Runc) Checkpoint(context context.Context, id string, opts *CheckpointOpts, actions ...CheckpointAction) error {
|
||||
args := []string{"checkpoint"}
|
||||
if opts != nil {
|
||||
args = append(args, opts.args()...)
|
||||
}
|
||||
for _, a := range actions {
|
||||
args = a(args)
|
||||
}
|
||||
return r.runOrError(r.command(context, append(args, id)...))
|
||||
}
|
||||
|
||||
type RestoreOpts struct {
|
||||
CheckpointOpts
|
||||
IO
|
||||
|
||||
Detach bool
|
||||
PidFile string
|
||||
NoSubreaper bool
|
||||
NoPivot bool
|
||||
}
|
||||
|
||||
func (o *RestoreOpts) args() ([]string, error) {
|
||||
out := o.CheckpointOpts.args()
|
||||
if o.Detach {
|
||||
out = append(out, "--detach")
|
||||
}
|
||||
if o.PidFile != "" {
|
||||
abs, err := filepath.Abs(o.PidFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, "--pid-file", abs)
|
||||
}
|
||||
if o.NoPivot {
|
||||
out = append(out, "--no-pivot")
|
||||
}
|
||||
if o.NoSubreaper {
|
||||
out = append(out, "-no-subreaper")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Restore restores a container with the provide id from an existing checkpoint
|
||||
func (r *Runc) Restore(context context.Context, id, bundle string, opts *RestoreOpts) (int, error) {
|
||||
args := []string{"restore"}
|
||||
if opts != nil {
|
||||
oargs, err := opts.args()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
args = append(args, oargs...)
|
||||
}
|
||||
args = append(args, "--bundle", bundle)
|
||||
cmd := r.command(context, append(args, id)...)
|
||||
if opts != nil {
|
||||
opts.Set(cmd)
|
||||
}
|
||||
if err := Monitor.Start(cmd); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return Monitor.Wait(cmd)
|
||||
}
|
||||
|
||||
// Update updates the current container with the provided resource spec
|
||||
func (r *Runc) Update(context context.Context, id string, resources *specs.LinuxResources) error {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if err := json.NewEncoder(buf).Encode(resources); err != nil {
|
||||
return err
|
||||
}
|
||||
args := []string{"update", "--resources", "-", id}
|
||||
cmd := r.command(context, args...)
|
||||
cmd.Stdin = buf
|
||||
return r.runOrError(cmd)
|
||||
}
|
||||
|
||||
func (r *Runc) args() (out []string) {
|
||||
if r.Root != "" {
|
||||
out = append(out, "--root", r.Root)
|
||||
}
|
||||
if r.Debug {
|
||||
out = append(out, "--debug")
|
||||
}
|
||||
if r.Log != "" {
|
||||
out = append(out, "--log", r.Log)
|
||||
}
|
||||
if r.LogFormat != none {
|
||||
out = append(out, "--log-format", string(r.LogFormat))
|
||||
}
|
||||
if r.Criu != "" {
|
||||
out = append(out, "--criu", r.Criu)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Runc) command(context context.Context, args ...string) *exec.Cmd {
|
||||
command := r.Command
|
||||
if command == "" {
|
||||
command = DefaultCommand
|
||||
}
|
||||
cmd := exec.CommandContext(context, command, append(r.args(), args...)...)
|
||||
if r.PdeathSignal != 0 {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Pdeathsig: r.PdeathSignal,
|
||||
}
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// runOrError will run the provided command. If an error is
|
||||
// encountered and neither Stdout or Stderr was set the error and the
|
||||
// stderr of the command will be returned in the format of <error>:
|
||||
// <stderr>
|
||||
func (r *Runc) runOrError(cmd *exec.Cmd) error {
|
||||
if cmd.Stdout != nil || cmd.Stderr != nil {
|
||||
return Monitor.Run(cmd)
|
||||
}
|
||||
data, err := Monitor.CombinedOutput(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", err, data)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package runc
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ReadPidFile reads the pid file at the provided path and returns
|
||||
// the pid or an error if the read and conversion is unsuccessful
|
||||
func ReadPidFile(path string) (int, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return strconv.Atoi(string(data))
|
||||
}
|
||||
|
||||
const exitSignalOffset = 128
|
||||
|
||||
// exitStatus returns the correct exit status for a process based on if it
|
||||
// was signaled or exited cleanly
|
||||
func exitStatus(status syscall.WaitStatus) int {
|
||||
if status.Signaled() {
|
||||
return exitSignalOffset + int(status.Signal())
|
||||
}
|
||||
return status.ExitStatus()
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
|
|
@ -1,110 +0,0 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
// The current operating system does not provide the required data for user lookups.
|
||||
ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data")
|
||||
// No matching entries found in file.
|
||||
ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
|
||||
ErrNoGroupEntries = errors.New("no matching entries in group file")
|
||||
)
|
||||
|
||||
func lookupUser(filter func(u User) bool) (User, error) {
|
||||
// Get operating system-specific passwd reader-closer.
|
||||
passwd, err := GetPasswd()
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
defer passwd.Close()
|
||||
|
||||
// Get the users.
|
||||
users, err := ParsePasswdFilter(passwd, filter)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
// No user entries found.
|
||||
if len(users) == 0 {
|
||||
return User{}, ErrNoPasswdEntries
|
||||
}
|
||||
|
||||
// Assume the first entry is the "correct" one.
|
||||
return users[0], nil
|
||||
}
|
||||
|
||||
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
|
||||
// user cannot be found (or there is no /etc/passwd file on the filesystem),
|
||||
// then CurrentUser returns an error.
|
||||
func CurrentUser() (User, error) {
|
||||
return LookupUid(syscall.Getuid())
|
||||
}
|
||||
|
||||
// LookupUser looks up a user by their username in /etc/passwd. If the user
|
||||
// cannot be found (or there is no /etc/passwd file on the filesystem), then
|
||||
// LookupUser returns an error.
|
||||
func LookupUser(username string) (User, error) {
|
||||
return lookupUser(func(u User) bool {
|
||||
return u.Name == username
|
||||
})
|
||||
}
|
||||
|
||||
// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
|
||||
// be found (or there is no /etc/passwd file on the filesystem), then LookupId
|
||||
// returns an error.
|
||||
func LookupUid(uid int) (User, error) {
|
||||
return lookupUser(func(u User) bool {
|
||||
return u.Uid == uid
|
||||
})
|
||||
}
|
||||
|
||||
func lookupGroup(filter func(g Group) bool) (Group, error) {
|
||||
// Get operating system-specific group reader-closer.
|
||||
group, err := GetGroup()
|
||||
if err != nil {
|
||||
return Group{}, err
|
||||
}
|
||||
defer group.Close()
|
||||
|
||||
// Get the users.
|
||||
groups, err := ParseGroupFilter(group, filter)
|
||||
if err != nil {
|
||||
return Group{}, err
|
||||
}
|
||||
|
||||
// No user entries found.
|
||||
if len(groups) == 0 {
|
||||
return Group{}, ErrNoGroupEntries
|
||||
}
|
||||
|
||||
// Assume the first entry is the "correct" one.
|
||||
return groups[0], nil
|
||||
}
|
||||
|
||||
// CurrentGroup looks up the current user's group by their primary group id's
|
||||
// entry in /etc/passwd. If the group cannot be found (or there is no
|
||||
// /etc/group file on the filesystem), then CurrentGroup returns an error.
|
||||
func CurrentGroup() (Group, error) {
|
||||
return LookupGid(syscall.Getgid())
|
||||
}
|
||||
|
||||
// LookupGroup looks up a group by its name in /etc/group. If the group cannot
|
||||
// be found (or there is no /etc/group file on the filesystem), then LookupGroup
|
||||
// returns an error.
|
||||
func LookupGroup(groupname string) (Group, error) {
|
||||
return lookupGroup(func(g Group) bool {
|
||||
return g.Name == groupname
|
||||
})
|
||||
}
|
||||
|
||||
// LookupGid looks up a group by its group id in /etc/group. If the group cannot
|
||||
// be found (or there is no /etc/group file on the filesystem), then LookupGid
|
||||
// returns an error.
|
||||
func LookupGid(gid int) (Group, error) {
|
||||
return lookupGroup(func(g Group) bool {
|
||||
return g.Gid == gid
|
||||
})
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Unix-specific path to the passwd and group formatted files.
|
||||
const (
|
||||
unixPasswdPath = "/etc/passwd"
|
||||
unixGroupPath = "/etc/group"
|
||||
)
|
||||
|
||||
func GetPasswdPath() (string, error) {
|
||||
return unixPasswdPath, nil
|
||||
}
|
||||
|
||||
func GetPasswd() (io.ReadCloser, error) {
|
||||
return os.Open(unixPasswdPath)
|
||||
}
|
||||
|
||||
func GetGroupPath() (string, error) {
|
||||
return unixGroupPath, nil
|
||||
}
|
||||
|
||||
func GetGroup() (io.ReadCloser, error) {
|
||||
return os.Open(unixGroupPath)
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||
|
||||
package user
|
||||
|
||||
import "io"
|
||||
|
||||
func GetPasswdPath() (string, error) {
|
||||
return "", ErrUnsupported
|
||||
}
|
||||
|
||||
func GetPasswd() (io.ReadCloser, error) {
|
||||
return nil, ErrUnsupported
|
||||
}
|
||||
|
||||
func GetGroupPath() (string, error) {
|
||||
return "", ErrUnsupported
|
||||
}
|
||||
|
||||
func GetGroup() (io.ReadCloser, error) {
|
||||
return nil, ErrUnsupported
|
||||
}
|
|
@ -1,441 +0,0 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
minId = 0
|
||||
maxId = 1<<31 - 1 //for 32-bit systems compatibility
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minId, maxId)
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Pass string
|
||||
Uid int
|
||||
Gid int
|
||||
Gecos string
|
||||
Home string
|
||||
Shell string
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
Name string
|
||||
Pass string
|
||||
Gid int
|
||||
List []string
|
||||
}
|
||||
|
||||
func parseLine(line string, v ...interface{}) {
|
||||
if line == "" {
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(line, ":")
|
||||
for i, p := range parts {
|
||||
// Ignore cases where we don't have enough fields to populate the arguments.
|
||||
// Some configuration files like to misbehave.
|
||||
if len(v) <= i {
|
||||
break
|
||||
}
|
||||
|
||||
// Use the type of the argument to figure out how to parse it, scanf() style.
|
||||
// This is legit.
|
||||
switch e := v[i].(type) {
|
||||
case *string:
|
||||
*e = p
|
||||
case *int:
|
||||
// "numbers", with conversion errors ignored because of some misbehaving configuration files.
|
||||
*e, _ = strconv.Atoi(p)
|
||||
case *[]string:
|
||||
// Comma-separated lists.
|
||||
if p != "" {
|
||||
*e = strings.Split(p, ",")
|
||||
} else {
|
||||
*e = []string{}
|
||||
}
|
||||
default:
|
||||
// Someone goof'd when writing code using this function. Scream so they can hear us.
|
||||
panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ParsePasswdFile(path string) ([]User, error) {
|
||||
passwd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer passwd.Close()
|
||||
return ParsePasswd(passwd)
|
||||
}
|
||||
|
||||
func ParsePasswd(passwd io.Reader) ([]User, error) {
|
||||
return ParsePasswdFilter(passwd, nil)
|
||||
}
|
||||
|
||||
func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
|
||||
passwd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer passwd.Close()
|
||||
return ParsePasswdFilter(passwd, filter)
|
||||
}
|
||||
|
||||
func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("nil source for passwd-formatted data")
|
||||
}
|
||||
|
||||
var (
|
||||
s = bufio.NewScanner(r)
|
||||
out = []User{}
|
||||
)
|
||||
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(s.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// see: man 5 passwd
|
||||
// name:password:UID:GID:GECOS:directory:shell
|
||||
// Name:Pass:Uid:Gid:Gecos:Home:Shell
|
||||
// root:x:0:0:root:/root:/bin/bash
|
||||
// adm:x:3:4:adm:/var/adm:/bin/false
|
||||
p := User{}
|
||||
parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell)
|
||||
|
||||
if filter == nil || filter(p) {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ParseGroupFile(path string) ([]Group, error) {
|
||||
group, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer group.Close()
|
||||
return ParseGroup(group)
|
||||
}
|
||||
|
||||
func ParseGroup(group io.Reader) ([]Group, error) {
|
||||
return ParseGroupFilter(group, nil)
|
||||
}
|
||||
|
||||
func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
|
||||
group, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer group.Close()
|
||||
return ParseGroupFilter(group, filter)
|
||||
}
|
||||
|
||||
func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("nil source for group-formatted data")
|
||||
}
|
||||
|
||||
var (
|
||||
s = bufio.NewScanner(r)
|
||||
out = []Group{}
|
||||
)
|
||||
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text := s.Text()
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// see: man 5 group
|
||||
// group_name:password:GID:user_list
|
||||
// Name:Pass:Gid:List
|
||||
// root:x:0:root
|
||||
// adm:x:4:root,adm,daemon
|
||||
p := Group{}
|
||||
parseLine(text, &p.Name, &p.Pass, &p.Gid, &p.List)
|
||||
|
||||
if filter == nil || filter(p) {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type ExecUser struct {
|
||||
Uid int
|
||||
Gid int
|
||||
Sgids []int
|
||||
Home string
|
||||
}
|
||||
|
||||
// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
|
||||
// given file paths and uses that data as the arguments to GetExecUser. If the
|
||||
// files cannot be opened for any reason, the error is ignored and a nil
|
||||
// io.Reader is passed instead.
|
||||
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
|
||||
passwd, err := os.Open(passwdPath)
|
||||
if err != nil {
|
||||
passwd = nil
|
||||
} else {
|
||||
defer passwd.Close()
|
||||
}
|
||||
|
||||
group, err := os.Open(groupPath)
|
||||
if err != nil {
|
||||
group = nil
|
||||
} else {
|
||||
defer group.Close()
|
||||
}
|
||||
|
||||
return GetExecUser(userSpec, defaults, passwd, group)
|
||||
}
|
||||
|
||||
// GetExecUser parses a user specification string (using the passwd and group
|
||||
// readers as sources for /etc/passwd and /etc/group data, respectively). In
|
||||
// the case of blank fields or missing data from the sources, the values in
|
||||
// defaults is used.
|
||||
//
|
||||
// GetExecUser will return an error if a user or group literal could not be
|
||||
// found in any entry in passwd and group respectively.
|
||||
//
|
||||
// Examples of valid user specifications are:
|
||||
// * ""
|
||||
// * "user"
|
||||
// * "uid"
|
||||
// * "user:group"
|
||||
// * "uid:gid
|
||||
// * "user:gid"
|
||||
// * "uid:group"
|
||||
//
|
||||
// It should be noted that if you specify a numeric user or group id, they will
|
||||
// not be evaluated as usernames (only the metadata will be filled). So attempting
|
||||
// to parse a user with user.Name = "1337" will produce the user with a UID of
|
||||
// 1337.
|
||||
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
|
||||
if defaults == nil {
|
||||
defaults = new(ExecUser)
|
||||
}
|
||||
|
||||
// Copy over defaults.
|
||||
user := &ExecUser{
|
||||
Uid: defaults.Uid,
|
||||
Gid: defaults.Gid,
|
||||
Sgids: defaults.Sgids,
|
||||
Home: defaults.Home,
|
||||
}
|
||||
|
||||
// Sgids slice *cannot* be nil.
|
||||
if user.Sgids == nil {
|
||||
user.Sgids = []int{}
|
||||
}
|
||||
|
||||
// Allow for userArg to have either "user" syntax, or optionally "user:group" syntax
|
||||
var userArg, groupArg string
|
||||
parseLine(userSpec, &userArg, &groupArg)
|
||||
|
||||
// Convert userArg and groupArg to be numeric, so we don't have to execute
|
||||
// Atoi *twice* for each iteration over lines.
|
||||
uidArg, uidErr := strconv.Atoi(userArg)
|
||||
gidArg, gidErr := strconv.Atoi(groupArg)
|
||||
|
||||
// Find the matching user.
|
||||
users, err := ParsePasswdFilter(passwd, func(u User) bool {
|
||||
if userArg == "" {
|
||||
// Default to current state of the user.
|
||||
return u.Uid == user.Uid
|
||||
}
|
||||
|
||||
if uidErr == nil {
|
||||
// If the userArg is numeric, always treat it as a UID.
|
||||
return uidArg == u.Uid
|
||||
}
|
||||
|
||||
return u.Name == userArg
|
||||
})
|
||||
|
||||
// If we can't find the user, we have to bail.
|
||||
if err != nil && passwd != nil {
|
||||
if userArg == "" {
|
||||
userArg = strconv.Itoa(user.Uid)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find user %s: %v", userArg, err)
|
||||
}
|
||||
|
||||
var matchedUserName string
|
||||
if len(users) > 0 {
|
||||
// First match wins, even if there's more than one matching entry.
|
||||
matchedUserName = users[0].Name
|
||||
user.Uid = users[0].Uid
|
||||
user.Gid = users[0].Gid
|
||||
user.Home = users[0].Home
|
||||
} else if userArg != "" {
|
||||
// If we can't find a user with the given username, the only other valid
|
||||
// option is if it's a numeric username with no associated entry in passwd.
|
||||
|
||||
if uidErr != nil {
|
||||
// Not numeric.
|
||||
return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries)
|
||||
}
|
||||
user.Uid = uidArg
|
||||
|
||||
// Must be inside valid uid range.
|
||||
if user.Uid < minId || user.Uid > maxId {
|
||||
return nil, ErrRange
|
||||
}
|
||||
|
||||
// Okay, so it's numeric. We can just roll with this.
|
||||
}
|
||||
|
||||
// On to the groups. If we matched a username, we need to do this because of
|
||||
// the supplementary group IDs.
|
||||
if groupArg != "" || matchedUserName != "" {
|
||||
groups, err := ParseGroupFilter(group, func(g Group) bool {
|
||||
// If the group argument isn't explicit, we'll just search for it.
|
||||
if groupArg == "" {
|
||||
// Check if user is a member of this group.
|
||||
for _, u := range g.List {
|
||||
if u == matchedUserName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if gidErr == nil {
|
||||
// If the groupArg is numeric, always treat it as a GID.
|
||||
return gidArg == g.Gid
|
||||
}
|
||||
|
||||
return g.Name == groupArg
|
||||
})
|
||||
if err != nil && group != nil {
|
||||
return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err)
|
||||
}
|
||||
|
||||
// Only start modifying user.Gid if it is in explicit form.
|
||||
if groupArg != "" {
|
||||
if len(groups) > 0 {
|
||||
// First match wins, even if there's more than one matching entry.
|
||||
user.Gid = groups[0].Gid
|
||||
} else if groupArg != "" {
|
||||
// If we can't find a group with the given name, the only other valid
|
||||
// option is if it's a numeric group name with no associated entry in group.
|
||||
|
||||
if gidErr != nil {
|
||||
// Not numeric.
|
||||
return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries)
|
||||
}
|
||||
user.Gid = gidArg
|
||||
|
||||
// Must be inside valid gid range.
|
||||
if user.Gid < minId || user.Gid > maxId {
|
||||
return nil, ErrRange
|
||||
}
|
||||
|
||||
// Okay, so it's numeric. We can just roll with this.
|
||||
}
|
||||
} else if len(groups) > 0 {
|
||||
// Supplementary group ids only make sense if in the implicit form.
|
||||
user.Sgids = make([]int, len(groups))
|
||||
for i, group := range groups {
|
||||
user.Sgids[i] = group.Gid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetAdditionalGroups looks up a list of groups by name or group id
|
||||
// against the given /etc/group formatted data. If a group name cannot
|
||||
// be found, an error will be returned. If a group id cannot be found,
|
||||
// or the given group data is nil, the id will be returned as-is
|
||||
// provided it is in the legal range.
|
||||
func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
|
||||
var groups = []Group{}
|
||||
if group != nil {
|
||||
var err error
|
||||
groups, err = ParseGroupFilter(group, func(g Group) bool {
|
||||
for _, ag := range additionalGroups {
|
||||
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err)
|
||||
}
|
||||
}
|
||||
|
||||
gidMap := make(map[int]struct{})
|
||||
for _, ag := range additionalGroups {
|
||||
var found bool
|
||||
for _, g := range groups {
|
||||
// if we found a matched group either by name or gid, take the
|
||||
// first matched as correct
|
||||
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||
if _, ok := gidMap[g.Gid]; !ok {
|
||||
gidMap[g.Gid] = struct{}{}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// we asked for a group but didn't find it. let's check to see
|
||||
// if we wanted a numeric group
|
||||
if !found {
|
||||
gid, err := strconv.Atoi(ag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to find group %s", ag)
|
||||
}
|
||||
// Ensure gid is inside gid range.
|
||||
if gid < minId || gid > maxId {
|
||||
return nil, ErrRange
|
||||
}
|
||||
gidMap[gid] = struct{}{}
|
||||
}
|
||||
}
|
||||
gids := []int{}
|
||||
for gid := range gidMap {
|
||||
gids = append(gids, gid)
|
||||
}
|
||||
return gids, nil
|
||||
}
|
||||
|
||||
// GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
|
||||
// that opens the groupPath given and gives it as an argument to
|
||||
// GetAdditionalGroups.
|
||||
func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
|
||||
group, err := os.Open(groupPath)
|
||||
if err == nil {
|
||||
defer group.Close()
|
||||
}
|
||||
return GetAdditionalGroups(additionalGroups, group)
|
||||
}
|
|
@ -17,7 +17,7 @@ type Spec struct {
|
|||
// Mounts configures additional mounts (on top of Root).
|
||||
Mounts []Mount `json:"mounts,omitempty"`
|
||||
// Hooks configures callbacks for container lifecycle events.
|
||||
Hooks Hooks `json:"hooks"`
|
||||
Hooks *Hooks `json:"hooks,omitempty"`
|
||||
// Annotations contains arbitrary metadata for the container.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
|
||||
|
@ -44,10 +44,10 @@ type Process struct {
|
|||
// Cwd is the current working directory for the process and must be
|
||||
// relative to the container's root.
|
||||
Cwd string `json:"cwd"`
|
||||
// Capabilities are Linux capabilities that are kept for the container.
|
||||
Capabilities []string `json:"capabilities,omitempty" platform:"linux"`
|
||||
// Capabilities are Linux capabilities that are kept for the process.
|
||||
Capabilities *LinuxCapabilities `json:"capabilities,omitempty" platform:"linux"`
|
||||
// Rlimits specifies rlimit options to apply to the process.
|
||||
Rlimits []Rlimit `json:"rlimits,omitempty" platform:"linux"`
|
||||
Rlimits []LinuxRlimit `json:"rlimits,omitempty" platform:"linux"`
|
||||
// NoNewPrivileges controls whether additional privileges could be gained by processes in the container.
|
||||
NoNewPrivileges bool `json:"noNewPrivileges,omitempty" platform:"linux"`
|
||||
// ApparmorProfile specifies the apparmor profile for the container.
|
||||
|
@ -56,6 +56,21 @@ type Process struct {
|
|||
SelinuxLabel string `json:"selinuxLabel,omitempty" platform:"linux"`
|
||||
}
|
||||
|
||||
// LinuxCapabilities specifies the whitelist of capabilities that are kept for a process.
|
||||
// http://man7.org/linux/man-pages/man7/capabilities.7.html
|
||||
type LinuxCapabilities struct {
|
||||
// Bounding is the set of capabilities checked by the kernel.
|
||||
Bounding []string `json:"bounding,omitempty" platform:"linux"`
|
||||
// Effective is the set of capabilities checked by the kernel.
|
||||
Effective []string `json:"effective,omitempty" platform:"linux"`
|
||||
// Inheritable is the capabilities preserved across execve.
|
||||
Inheritable []string `json:"inheritable,omitempty" platform:"linux"`
|
||||
// Permitted is the limiting superset for effective capabilities.
|
||||
Permitted []string `json:"permitted,omitempty" platform:"linux"`
|
||||
// Ambient is the ambient set of capabilities that are kept.
|
||||
Ambient []string `json:"ambient,omitempty" platform:"linux"`
|
||||
}
|
||||
|
||||
// Box specifies dimensions of a rectangle. Used for specifying the size of a console.
|
||||
type Box struct {
|
||||
// Height is the vertical dimension of a box.
|
||||
|
@ -98,10 +113,10 @@ type Mount struct {
|
|||
// Destination is the path where the mount will be placed relative to the container's root. The path and child directories MUST exist, a runtime MUST NOT create directories automatically to a mount point.
|
||||
Destination string `json:"destination"`
|
||||
// Type specifies the mount kind.
|
||||
Type string `json:"type"`
|
||||
Type string `json:"type,omitempty"`
|
||||
// Source specifies the source path of the mount. In the case of bind mounts on
|
||||
// Linux based systems this would be the file on the host.
|
||||
Source string `json:"source"`
|
||||
Source string `json:"source,omitempty"`
|
||||
// Options are fstab style mount options.
|
||||
Options []string `json:"options,omitempty"`
|
||||
}
|
||||
|
@ -128,24 +143,24 @@ type Hooks struct {
|
|||
// Linux contains platform specific configuration for Linux based containers.
|
||||
type Linux struct {
|
||||
// UIDMapping specifies user mappings for supporting user namespaces on Linux.
|
||||
UIDMappings []IDMapping `json:"uidMappings,omitempty"`
|
||||
UIDMappings []LinuxIDMapping `json:"uidMappings,omitempty"`
|
||||
// GIDMapping specifies group mappings for supporting user namespaces on Linux.
|
||||
GIDMappings []IDMapping `json:"gidMappings,omitempty"`
|
||||
GIDMappings []LinuxIDMapping `json:"gidMappings,omitempty"`
|
||||
// Sysctl are a set of key value pairs that are set for the container on start
|
||||
Sysctl map[string]string `json:"sysctl,omitempty"`
|
||||
// Resources contain cgroup information for handling resource constraints
|
||||
// for the container
|
||||
Resources *Resources `json:"resources,omitempty"`
|
||||
Resources *LinuxResources `json:"resources,omitempty"`
|
||||
// CgroupsPath specifies the path to cgroups that are created and/or joined by the container.
|
||||
// The path is expected to be relative to the cgroups mountpoint.
|
||||
// If resources are specified, the cgroups at CgroupsPath will be updated based on resources.
|
||||
CgroupsPath *string `json:"cgroupsPath,omitempty"`
|
||||
CgroupsPath string `json:"cgroupsPath,omitempty"`
|
||||
// Namespaces contains the namespaces that are created and/or joined by the container
|
||||
Namespaces []Namespace `json:"namespaces,omitempty"`
|
||||
Namespaces []LinuxNamespace `json:"namespaces,omitempty"`
|
||||
// Devices are a list of device nodes that are created for the container
|
||||
Devices []Device `json:"devices,omitempty"`
|
||||
Devices []LinuxDevice `json:"devices,omitempty"`
|
||||
// Seccomp specifies the seccomp security settings for the container.
|
||||
Seccomp *Seccomp `json:"seccomp,omitempty"`
|
||||
Seccomp *LinuxSeccomp `json:"seccomp,omitempty"`
|
||||
// RootfsPropagation is the rootfs mount propagation mode for the container.
|
||||
RootfsPropagation string `json:"rootfsPropagation,omitempty"`
|
||||
// MaskedPaths masks over the provided paths inside the container.
|
||||
|
@ -156,21 +171,21 @@ type Linux struct {
|
|||
MountLabel string `json:"mountLabel,omitempty"`
|
||||
}
|
||||
|
||||
// Namespace is the configuration for a Linux namespace
|
||||
type Namespace struct {
|
||||
// LinuxNamespace is the configuration for a Linux namespace
|
||||
type LinuxNamespace struct {
|
||||
// Type is the type of Linux namespace
|
||||
Type NamespaceType `json:"type"`
|
||||
Type LinuxNamespaceType `json:"type"`
|
||||
// Path is a path to an existing namespace persisted on disk that can be joined
|
||||
// and is of the same type
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// NamespaceType is one of the Linux namespaces
|
||||
type NamespaceType string
|
||||
// LinuxNamespaceType is one of the Linux namespaces
|
||||
type LinuxNamespaceType string
|
||||
|
||||
const (
|
||||
// PIDNamespace for isolating process IDs
|
||||
PIDNamespace NamespaceType = "pid"
|
||||
PIDNamespace LinuxNamespaceType = "pid"
|
||||
// NetworkNamespace for isolating network devices, stacks, ports, etc
|
||||
NetworkNamespace = "network"
|
||||
// MountNamespace for isolating mount points
|
||||
|
@ -185,18 +200,18 @@ const (
|
|||
CgroupNamespace = "cgroup"
|
||||
)
|
||||
|
||||
// IDMapping specifies UID/GID mappings
|
||||
type IDMapping struct {
|
||||
// HostID is the UID/GID of the host user or group
|
||||
// LinuxIDMapping specifies UID/GID mappings
|
||||
type LinuxIDMapping struct {
|
||||
// HostID is the starting UID/GID on the host to be mapped to 'ContainerID'
|
||||
HostID uint32 `json:"hostID"`
|
||||
// ContainerID is the UID/GID of the container's user or group
|
||||
// ContainerID is the starting UID/GID in the container
|
||||
ContainerID uint32 `json:"containerID"`
|
||||
// Size is the length of the range of IDs mapped between the two namespaces
|
||||
// Size is the number of IDs to be mapped
|
||||
Size uint32 `json:"size"`
|
||||
}
|
||||
|
||||
// Rlimit type and restrictions
|
||||
type Rlimit struct {
|
||||
// LinuxRlimit type and restrictions
|
||||
type LinuxRlimit struct {
|
||||
// Type of the rlimit to set
|
||||
Type string `json:"type"`
|
||||
// Hard is the hard limit for the specified type
|
||||
|
@ -205,66 +220,66 @@ type Rlimit struct {
|
|||
Soft uint64 `json:"soft"`
|
||||
}
|
||||
|
||||
// HugepageLimit structure corresponds to limiting kernel hugepages
|
||||
type HugepageLimit struct {
|
||||
// LinuxHugepageLimit structure corresponds to limiting kernel hugepages
|
||||
type LinuxHugepageLimit struct {
|
||||
// Pagesize is the hugepage size
|
||||
Pagesize *string `json:"pageSize,omitempty"`
|
||||
Pagesize string `json:"pageSize"`
|
||||
// Limit is the limit of "hugepagesize" hugetlb usage
|
||||
Limit *uint64 `json:"limit,omitempty"`
|
||||
Limit uint64 `json:"limit"`
|
||||
}
|
||||
|
||||
// InterfacePriority for network interfaces
|
||||
type InterfacePriority struct {
|
||||
// LinuxInterfacePriority for network interfaces
|
||||
type LinuxInterfacePriority struct {
|
||||
// Name is the name of the network interface
|
||||
Name string `json:"name"`
|
||||
// Priority for the interface
|
||||
Priority uint32 `json:"priority"`
|
||||
}
|
||||
|
||||
// blockIODevice holds major:minor format supported in blkio cgroup
|
||||
type blockIODevice struct {
|
||||
// linuxBlockIODevice holds major:minor format supported in blkio cgroup
|
||||
type linuxBlockIODevice struct {
|
||||
// Major is the device's major number.
|
||||
Major int64 `json:"major"`
|
||||
// Minor is the device's minor number.
|
||||
Minor int64 `json:"minor"`
|
||||
}
|
||||
|
||||
// WeightDevice struct holds a `major:minor weight` pair for blkioWeightDevice
|
||||
type WeightDevice struct {
|
||||
blockIODevice
|
||||
// LinuxWeightDevice struct holds a `major:minor weight` pair for blkioWeightDevice
|
||||
type LinuxWeightDevice struct {
|
||||
linuxBlockIODevice
|
||||
// Weight is the bandwidth rate for the device, range is from 10 to 1000
|
||||
Weight *uint16 `json:"weight,omitempty"`
|
||||
// LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only
|
||||
LeafWeight *uint16 `json:"leafWeight,omitempty"`
|
||||
}
|
||||
|
||||
// ThrottleDevice struct holds a `major:minor rate_per_second` pair
|
||||
type ThrottleDevice struct {
|
||||
blockIODevice
|
||||
// LinuxThrottleDevice struct holds a `major:minor rate_per_second` pair
|
||||
type LinuxThrottleDevice struct {
|
||||
linuxBlockIODevice
|
||||
// Rate is the IO rate limit per cgroup per device
|
||||
Rate *uint64 `json:"rate,omitempty"`
|
||||
Rate uint64 `json:"rate"`
|
||||
}
|
||||
|
||||
// BlockIO for Linux cgroup 'blkio' resource management
|
||||
type BlockIO struct {
|
||||
// LinuxBlockIO for Linux cgroup 'blkio' resource management
|
||||
type LinuxBlockIO struct {
|
||||
// Specifies per cgroup weight, range is from 10 to 1000
|
||||
Weight *uint16 `json:"blkioWeight,omitempty"`
|
||||
// Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only
|
||||
LeafWeight *uint16 `json:"blkioLeafWeight,omitempty"`
|
||||
// Weight per cgroup per device, can override BlkioWeight
|
||||
WeightDevice []WeightDevice `json:"blkioWeightDevice,omitempty"`
|
||||
WeightDevice []LinuxWeightDevice `json:"blkioWeightDevice,omitempty"`
|
||||
// IO read rate limit per cgroup per device, bytes per second
|
||||
ThrottleReadBpsDevice []ThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"`
|
||||
ThrottleReadBpsDevice []LinuxThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"`
|
||||
// IO write rate limit per cgroup per device, bytes per second
|
||||
ThrottleWriteBpsDevice []ThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"`
|
||||
ThrottleWriteBpsDevice []LinuxThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"`
|
||||
// IO read rate limit per cgroup per device, IO per second
|
||||
ThrottleReadIOPSDevice []ThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"`
|
||||
ThrottleReadIOPSDevice []LinuxThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"`
|
||||
// IO write rate limit per cgroup per device, IO per second
|
||||
ThrottleWriteIOPSDevice []ThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"`
|
||||
ThrottleWriteIOPSDevice []LinuxThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"`
|
||||
}
|
||||
|
||||
// Memory for Linux cgroup 'memory' resource management
|
||||
type Memory struct {
|
||||
// LinuxMemory for Linux cgroup 'memory' resource management
|
||||
type LinuxMemory struct {
|
||||
// Memory limit (in bytes).
|
||||
Limit *uint64 `json:"limit,omitempty"`
|
||||
// Memory reservation or soft_limit (in bytes).
|
||||
|
@ -279,62 +294,62 @@ type Memory struct {
|
|||
Swappiness *uint64 `json:"swappiness,omitempty"`
|
||||
}
|
||||
|
||||
// CPU for Linux cgroup 'cpu' resource management
|
||||
type CPU struct {
|
||||
// LinuxCPU for Linux cgroup 'cpu' resource management
|
||||
type LinuxCPU struct {
|
||||
// CPU shares (relative weight (ratio) vs. other cgroups with cpu shares).
|
||||
Shares *uint64 `json:"shares,omitempty"`
|
||||
// CPU hardcap limit (in usecs). Allowed cpu time in a given period.
|
||||
Quota *uint64 `json:"quota,omitempty"`
|
||||
Quota *int64 `json:"quota,omitempty"`
|
||||
// CPU period to be used for hardcapping (in usecs).
|
||||
Period *uint64 `json:"period,omitempty"`
|
||||
// How much time realtime scheduling may use (in usecs).
|
||||
RealtimeRuntime *uint64 `json:"realtimeRuntime,omitempty"`
|
||||
RealtimeRuntime *int64 `json:"realtimeRuntime,omitempty"`
|
||||
// CPU period to be used for realtime scheduling (in usecs).
|
||||
RealtimePeriod *uint64 `json:"realtimePeriod,omitempty"`
|
||||
// CPUs to use within the cpuset. Default is to use any CPU available.
|
||||
Cpus *string `json:"cpus,omitempty"`
|
||||
Cpus string `json:"cpus,omitempty"`
|
||||
// List of memory nodes in the cpuset. Default is to use any available memory node.
|
||||
Mems *string `json:"mems,omitempty"`
|
||||
Mems string `json:"mems,omitempty"`
|
||||
}
|
||||
|
||||
// Pids for Linux cgroup 'pids' resource management (Linux 4.3)
|
||||
type Pids struct {
|
||||
// LinuxPids for Linux cgroup 'pids' resource management (Linux 4.3)
|
||||
type LinuxPids struct {
|
||||
// Maximum number of PIDs. Default is "no limit".
|
||||
Limit *int64 `json:"limit,omitempty"`
|
||||
Limit int64 `json:"limit"`
|
||||
}
|
||||
|
||||
// Network identification and priority configuration
|
||||
type Network struct {
|
||||
// LinuxNetwork identification and priority configuration
|
||||
type LinuxNetwork struct {
|
||||
// Set class identifier for container's network packets
|
||||
ClassID *uint32 `json:"classID,omitempty"`
|
||||
// Set priority of network traffic for container
|
||||
Priorities []InterfacePriority `json:"priorities,omitempty"`
|
||||
Priorities []LinuxInterfacePriority `json:"priorities,omitempty"`
|
||||
}
|
||||
|
||||
// Resources has container runtime resource constraints
|
||||
type Resources struct {
|
||||
// LinuxResources has container runtime resource constraints
|
||||
type LinuxResources struct {
|
||||
// Devices configures the device whitelist.
|
||||
Devices []DeviceCgroup `json:"devices,omitempty"`
|
||||
Devices []LinuxDeviceCgroup `json:"devices,omitempty"`
|
||||
// DisableOOMKiller disables the OOM killer for out of memory conditions
|
||||
DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"`
|
||||
// Specify an oom_score_adj for the container.
|
||||
OOMScoreAdj *int `json:"oomScoreAdj,omitempty"`
|
||||
// Memory restriction configuration
|
||||
Memory *Memory `json:"memory,omitempty"`
|
||||
Memory *LinuxMemory `json:"memory,omitempty"`
|
||||
// CPU resource restriction configuration
|
||||
CPU *CPU `json:"cpu,omitempty"`
|
||||
CPU *LinuxCPU `json:"cpu,omitempty"`
|
||||
// Task resource restriction configuration.
|
||||
Pids *Pids `json:"pids,omitempty"`
|
||||
Pids *LinuxPids `json:"pids,omitempty"`
|
||||
// BlockIO restriction configuration
|
||||
BlockIO *BlockIO `json:"blockIO,omitempty"`
|
||||
BlockIO *LinuxBlockIO `json:"blockIO,omitempty"`
|
||||
// Hugetlb limit (in bytes)
|
||||
HugepageLimits []HugepageLimit `json:"hugepageLimits,omitempty"`
|
||||
HugepageLimits []LinuxHugepageLimit `json:"hugepageLimits,omitempty"`
|
||||
// Network restriction configuration
|
||||
Network *Network `json:"network,omitempty"`
|
||||
Network *LinuxNetwork `json:"network,omitempty"`
|
||||
}
|
||||
|
||||
// Device represents the mknod information for a Linux special device file
|
||||
type Device struct {
|
||||
// LinuxDevice represents the mknod information for a Linux special device file
|
||||
type LinuxDevice struct {
|
||||
// Path to the device.
|
||||
Path string `json:"path"`
|
||||
// Device type, block, char, etc.
|
||||
|
@ -351,25 +366,18 @@ type Device struct {
|
|||
GID *uint32 `json:"gid,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceCgroup represents a device rule for the whitelist controller
|
||||
type DeviceCgroup struct {
|
||||
// LinuxDeviceCgroup represents a device rule for the whitelist controller
|
||||
type LinuxDeviceCgroup struct {
|
||||
// Allow or deny
|
||||
Allow bool `json:"allow"`
|
||||
// Device type, block, char, etc.
|
||||
Type *string `json:"type,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
// Major is the device's major number.
|
||||
Major *int64 `json:"major,omitempty"`
|
||||
// Minor is the device's minor number.
|
||||
Minor *int64 `json:"minor,omitempty"`
|
||||
// Cgroup access permissions format, rwm.
|
||||
Access *string `json:"access,omitempty"`
|
||||
}
|
||||
|
||||
// Seccomp represents syscall restrictions
|
||||
type Seccomp struct {
|
||||
DefaultAction Action `json:"defaultAction"`
|
||||
Architectures []Arch `json:"architectures"`
|
||||
Syscalls []Syscall `json:"syscalls,omitempty"`
|
||||
Access string `json:"access,omitempty"`
|
||||
}
|
||||
|
||||
// Solaris contains platform specific configuration for Solaris application containers.
|
||||
|
@ -381,26 +389,26 @@ type Solaris struct {
|
|||
// The maximum amount of shared memory allowed for this container.
|
||||
MaxShmMemory string `json:"maxShmMemory,omitempty"`
|
||||
// Specification for automatic creation of network resources for this container.
|
||||
Anet []Anet `json:"anet,omitempty"`
|
||||
Anet []SolarisAnet `json:"anet,omitempty"`
|
||||
// Set limit on the amount of CPU time that can be used by container.
|
||||
CappedCPU *CappedCPU `json:"cappedCPU,omitempty"`
|
||||
CappedCPU *SolarisCappedCPU `json:"cappedCPU,omitempty"`
|
||||
// The physical and swap caps on the memory that can be used by this container.
|
||||
CappedMemory *CappedMemory `json:"cappedMemory,omitempty"`
|
||||
CappedMemory *SolarisCappedMemory `json:"cappedMemory,omitempty"`
|
||||
}
|
||||
|
||||
// CappedCPU allows users to set limit on the amount of CPU time that can be used by container.
|
||||
type CappedCPU struct {
|
||||
// SolarisCappedCPU allows users to set limit on the amount of CPU time that can be used by container.
|
||||
type SolarisCappedCPU struct {
|
||||
Ncpus string `json:"ncpus,omitempty"`
|
||||
}
|
||||
|
||||
// CappedMemory allows users to set the physical and swap caps on the memory that can be used by this container.
|
||||
type CappedMemory struct {
|
||||
// SolarisCappedMemory allows users to set the physical and swap caps on the memory that can be used by this container.
|
||||
type SolarisCappedMemory struct {
|
||||
Physical string `json:"physical,omitempty"`
|
||||
Swap string `json:"swap,omitempty"`
|
||||
}
|
||||
|
||||
// Anet provides the specification for automatic creation of network resources for this container.
|
||||
type Anet struct {
|
||||
// SolarisAnet provides the specification for automatic creation of network resources for this container.
|
||||
type SolarisAnet struct {
|
||||
// Specify a name for the automatically created VNIC datalink.
|
||||
Linkname string `json:"linkname,omitempty"`
|
||||
// Specify the link over which the VNIC will be created.
|
||||
|
@ -469,6 +477,13 @@ type WindowsNetworkResources struct {
|
|||
EgressBandwidth *uint64 `json:"egressBandwidth,omitempty"`
|
||||
}
|
||||
|
||||
// LinuxSeccomp represents syscall restrictions
|
||||
type LinuxSeccomp struct {
|
||||
DefaultAction LinuxSeccompAction `json:"defaultAction"`
|
||||
Architectures []Arch `json:"architectures,omitempty"`
|
||||
Syscalls []LinuxSyscall `json:"syscalls"`
|
||||
}
|
||||
|
||||
// Arch used for additional architectures
|
||||
type Arch string
|
||||
|
||||
|
@ -491,45 +506,48 @@ const (
|
|||
ArchPPC64LE Arch = "SCMP_ARCH_PPC64LE"
|
||||
ArchS390 Arch = "SCMP_ARCH_S390"
|
||||
ArchS390X Arch = "SCMP_ARCH_S390X"
|
||||
ArchPARISC Arch = "SCMP_ARCH_PARISC"
|
||||
ArchPARISC64 Arch = "SCMP_ARCH_PARISC64"
|
||||
)
|
||||
|
||||
// Action taken upon Seccomp rule match
|
||||
type Action string
|
||||
// LinuxSeccompAction taken upon Seccomp rule match
|
||||
type LinuxSeccompAction string
|
||||
|
||||
// Define actions for Seccomp rules
|
||||
const (
|
||||
ActKill Action = "SCMP_ACT_KILL"
|
||||
ActTrap Action = "SCMP_ACT_TRAP"
|
||||
ActErrno Action = "SCMP_ACT_ERRNO"
|
||||
ActTrace Action = "SCMP_ACT_TRACE"
|
||||
ActAllow Action = "SCMP_ACT_ALLOW"
|
||||
ActKill LinuxSeccompAction = "SCMP_ACT_KILL"
|
||||
ActTrap LinuxSeccompAction = "SCMP_ACT_TRAP"
|
||||
ActErrno LinuxSeccompAction = "SCMP_ACT_ERRNO"
|
||||
ActTrace LinuxSeccompAction = "SCMP_ACT_TRACE"
|
||||
ActAllow LinuxSeccompAction = "SCMP_ACT_ALLOW"
|
||||
)
|
||||
|
||||
// Operator used to match syscall arguments in Seccomp
|
||||
type Operator string
|
||||
// LinuxSeccompOperator used to match syscall arguments in Seccomp
|
||||
type LinuxSeccompOperator string
|
||||
|
||||
// Define operators for syscall arguments in Seccomp
|
||||
const (
|
||||
OpNotEqual Operator = "SCMP_CMP_NE"
|
||||
OpLessThan Operator = "SCMP_CMP_LT"
|
||||
OpLessEqual Operator = "SCMP_CMP_LE"
|
||||
OpEqualTo Operator = "SCMP_CMP_EQ"
|
||||
OpGreaterEqual Operator = "SCMP_CMP_GE"
|
||||
OpGreaterThan Operator = "SCMP_CMP_GT"
|
||||
OpMaskedEqual Operator = "SCMP_CMP_MASKED_EQ"
|
||||
OpNotEqual LinuxSeccompOperator = "SCMP_CMP_NE"
|
||||
OpLessThan LinuxSeccompOperator = "SCMP_CMP_LT"
|
||||
OpLessEqual LinuxSeccompOperator = "SCMP_CMP_LE"
|
||||
OpEqualTo LinuxSeccompOperator = "SCMP_CMP_EQ"
|
||||
OpGreaterEqual LinuxSeccompOperator = "SCMP_CMP_GE"
|
||||
OpGreaterThan LinuxSeccompOperator = "SCMP_CMP_GT"
|
||||
OpMaskedEqual LinuxSeccompOperator = "SCMP_CMP_MASKED_EQ"
|
||||
)
|
||||
|
||||
// Arg used for matching specific syscall arguments in Seccomp
|
||||
type Arg struct {
|
||||
Index uint `json:"index"`
|
||||
Value uint64 `json:"value"`
|
||||
ValueTwo uint64 `json:"valueTwo"`
|
||||
Op Operator `json:"op"`
|
||||
// LinuxSeccompArg used for matching specific syscall arguments in Seccomp
|
||||
type LinuxSeccompArg struct {
|
||||
Index uint `json:"index"`
|
||||
Value uint64 `json:"value"`
|
||||
ValueTwo uint64 `json:"valueTwo"`
|
||||
Op LinuxSeccompOperator `json:"op"`
|
||||
}
|
||||
|
||||
// Syscall is used to match a syscall in Seccomp
|
||||
type Syscall struct {
|
||||
Name string `json:"name"`
|
||||
Action Action `json:"action"`
|
||||
Args []Arg `json:"args,omitempty"`
|
||||
// LinuxSyscall is used to match a syscall in Seccomp
|
||||
type LinuxSyscall struct {
|
||||
Names []string `json:"names"`
|
||||
Action LinuxSeccompAction `json:"action"`
|
||||
Args []LinuxSeccompArg `json:"args"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
|
|
@ -3,15 +3,15 @@ package specs
|
|||
// State holds information about the runtime state of the container.
|
||||
type State struct {
|
||||
// Version is the version of the specification that is supported.
|
||||
Version string `json:"version"`
|
||||
Version string `json:"ociVersion"`
|
||||
// ID is the container ID
|
||||
ID string `json:"id"`
|
||||
// Status is the runtime state of the container.
|
||||
// Status is the runtime status of the container.
|
||||
Status string `json:"status"`
|
||||
// Pid is the process ID for the container process.
|
||||
Pid int `json:"pid"`
|
||||
// BundlePath is the path to the container's bundle directory.
|
||||
BundlePath string `json:"bundlePath"`
|
||||
// Annotations are the annotations associated with the container.
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
// Bundle is the path to the container's bundle directory.
|
||||
Bundle string `json:"bundle"`
|
||||
// Annotations are key values associated with the container.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ const (
|
|||
VersionPatch = 0
|
||||
|
||||
// VersionDev indicates development branch. Releases will be empty string.
|
||||
VersionDev = "-rc2-dev"
|
||||
VersionDev = "-rc5"
|
||||
)
|
||||
|
||||
// Version is the specification version that the package types support.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package fifo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
@ -8,7 +9,6 @@ import (
|
|||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type fifo struct {
|
||||
|
|
Loading…
Reference in New Issue