Add basic skeleton of libpod runtime

Signed-off-by: Matthew Heon <mheon@redhat.com>
This commit is contained in:
Matthew Heon 2017-08-28 17:33:02 -04:00
parent 3473e8afed
commit 2a121111b5
5 changed files with 715 additions and 28 deletions

View file

@ -4,14 +4,13 @@ import (
"fmt" "fmt"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/kubernetes-incubator/cri-o/libpod/ctr" "github.com/kubernetes-incubator/cri-o/libpod/ctr"
"github.com/kubernetes-incubator/cri-o/libpod/pod" "github.com/kubernetes-incubator/cri-o/libpod/pod"
) )
var ( var (
runtimeNotImplemented = func(rt *Runtime) error { errRuntimeFinalized = fmt.Errorf("runtime has already been finalized")
return fmt.Errorf("NOT IMPLEMENTED")
}
ctrNotImplemented = func(c *ctr.Container) error { ctrNotImplemented = func(c *ctr.Container) error {
return fmt.Errorf("NOT IMPLEMENTED") return fmt.Errorf("NOT IMPLEMENTED")
} }
@ -37,15 +36,48 @@ const (
// WithStorageConfig uses the given configuration to set up container storage // WithStorageConfig uses the given configuration to set up container storage
// If this is not specified, the system default configuration will be used // If this is not specified, the system default configuration will be used
// instead // instead
func WithStorageConfig(config *storage.StoreOptions) RuntimeOption { func WithStorageConfig(config storage.StoreOptions) RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
if rt.valid {
return errRuntimeFinalized
}
rt.config.StorageConfig.RunRoot = config.RunRoot
rt.config.StorageConfig.GraphRoot = config.GraphRoot
rt.config.StorageConfig.GraphDriverName = config.GraphDriverName
rt.config.StorageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions))
copy(rt.config.StorageConfig.GraphDriverOptions, config.GraphDriverOptions)
rt.config.StorageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap))
copy(rt.config.StorageConfig.UIDMap, config.UIDMap)
rt.config.StorageConfig.GIDMap = make([]idtools.IDMap, len(config.UIDMap))
copy(rt.config.StorageConfig.GIDMap, config.GIDMap)
return nil
}
} }
// WithImageConfig uses the given configuration to set up image handling // WithImageConfig uses the given configuration to set up image handling
// If this is not specified, the system default configuration will be used // If this is not specified, the system default configuration will be used
// instead // instead
func WithImageConfig(defaultTransport string, insecureRegistries, registries []string) RuntimeOption { func WithImageConfig(defaultTransport string, insecureRegistries, registries []string) RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
if rt.valid {
return errRuntimeFinalized
}
rt.config.ImageDefaultTransport = defaultTransport
rt.config.InsecureRegistries = make([]string, len(insecureRegistries))
copy(rt.config.InsecureRegistries, insecureRegistries)
rt.config.Registries = make([]string, len(registries))
copy(rt.config.Registries, registries)
return nil
}
} }
// WithSignaturePolicy specifies the path of a file which decides how trust is // WithSignaturePolicy specifies the path of a file which decides how trust is
@ -53,52 +85,97 @@ func WithImageConfig(defaultTransport string, insecureRegistries, registries []s
// If this is not specified, the system default configuration will be used // If this is not specified, the system default configuration will be used
// instead // instead
func WithSignaturePolicy(path string) RuntimeOption { func WithSignaturePolicy(path string) RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
if rt.valid {
return errRuntimeFinalized
}
rt.config.SignaturePolicyPath = path
return nil
}
} }
// WithOCIRuntime specifies an OCI runtime to use for running containers // WithOCIRuntime specifies an OCI runtime to use for running containers
func WithOCIRuntime(runtimePath string) RuntimeOption { func WithOCIRuntime(runtimePath string) RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
if rt.valid {
return errRuntimeFinalized
}
rt.config.RuntimePath = runtimePath
return nil
}
} }
// WithConmonPath specifies the path to the conmon binary which manages the // WithConmonPath specifies the path to the conmon binary which manages the
// runtime // runtime
func WithConmonPath(path string) RuntimeOption { func WithConmonPath(path string) RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
if rt.valid {
return errRuntimeFinalized
}
rt.config.ConmonPath = path
return nil
}
} }
// WithConmonEnv specifies the environment variable list for the conmon process // WithConmonEnv specifies the environment variable list for the conmon process
func WithConmonEnv(environment []string) RuntimeOption { func WithConmonEnv(environment []string) RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
if rt.valid {
return errRuntimeFinalized
}
rt.config.ConmonEnvVars = make([]string, len(environment))
copy(rt.config.ConmonEnvVars, environment)
return nil
}
} }
// WithCgroupManager specifies the manager implementation name which is used to // WithCgroupManager specifies the manager implementation name which is used to
// handle cgroups for containers // handle cgroups for containers
func WithCgroupManager(manager string) RuntimeOption { func WithCgroupManager(manager string) RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
if rt.valid {
return errRuntimeFinalized
}
rt.config.CgroupManager = manager
return nil
}
} }
// WithSELinux enables SELinux on the container server // WithSELinux enables SELinux on the container server
func WithSELinux() RuntimeOption { func WithSELinux() RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
} if rt.valid {
return errRuntimeFinalized
}
// WithApparmorProfile specifies the apparmor profile name which will be used as rt.config.SelinuxEnabled = true
// the default for created containers
func WithApparmorProfile(profile string) RuntimeOption {
return runtimeNotImplemented
}
// WithSeccompProfile specifies the seccomp profile which will be used as the return nil
// default for created containers }
func WithSeccompProfile(profilePath string) RuntimeOption {
return runtimeNotImplemented
} }
// WithPidsLimit specifies the maximum number of processes each container is // WithPidsLimit specifies the maximum number of processes each container is
// restricted to // restricted to
func WithPidsLimit(limit int64) RuntimeOption { func WithPidsLimit(limit int64) RuntimeOption {
return runtimeNotImplemented return func(rt *Runtime) error {
if rt.valid {
return errRuntimeFinalized
}
rt.config.PidsLimit = limit
return nil
}
} }
// Container Creation Options // Container Creation Options

View file

@ -1,26 +1,135 @@
package libpod package libpod
import ( import (
"fmt"
"sync"
"github.com/containers/image/types"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/libpod/ctr" "github.com/kubernetes-incubator/cri-o/libpod/ctr"
"github.com/kubernetes-incubator/cri-o/libpod/pod" "github.com/kubernetes-incubator/cri-o/libpod/pod"
"github.com/kubernetes-incubator/cri-o/server/apparmor"
"github.com/kubernetes-incubator/cri-o/server/seccomp"
spec "github.com/opencontainers/runtime-spec/specs-go" spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/ulule/deepcopier"
) )
// Runtime API
// A RuntimeOption is a functional option which alters the Runtime created by // A RuntimeOption is a functional option which alters the Runtime created by
// NewRuntime // NewRuntime
type RuntimeOption func(*Runtime) error type RuntimeOption func(*Runtime) error
// Runtime is the core libpod runtime // Runtime is the core libpod runtime
type Runtime struct { type Runtime struct {
// TODO populate config *RuntimeConfig
store storage.Store
imageContext *types.SystemContext
apparmorEnabled bool
seccompEnabled bool
valid bool
lock sync.RWMutex
} }
// RuntimeConfig contains configuration options used to set up the runtime
type RuntimeConfig struct {
StorageConfig storage.StoreOptions
ImageDefaultTransport string
InsecureRegistries []string
Registries []string
SignaturePolicyPath string
RuntimePath string
ConmonPath string
ConmonEnvVars []string
CgroupManager string
SelinuxEnabled bool
PidsLimit int64
}
var (
defaultRuntimeConfig = RuntimeConfig{
// Leave this empty so containers/storage will use its defaults
StorageConfig: storage.StoreOptions{},
ImageDefaultTransport: "docker://",
RuntimePath: "/usr/bin/runc",
ConmonPath: "/usr/local/libexec/crio/conmon",
ConmonEnvVars: []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
},
CgroupManager: "cgroupfs",
SelinuxEnabled: false,
PidsLimit: 1024,
}
)
// NewRuntime creates a new container runtime // NewRuntime creates a new container runtime
// Options can be passed to override the default configuration for the runtime
func NewRuntime(options ...RuntimeOption) (*Runtime, error) { func NewRuntime(options ...RuntimeOption) (*Runtime, error) {
return nil, ctr.ErrNotImplemented runtime := new(Runtime)
runtime.config = new(RuntimeConfig)
// Copy the default configuration
deepcopier.Copy(defaultRuntimeConfig).To(runtime.config)
// Overwrite it with user-given configuration options
for _, opt := range options {
if err := opt(runtime); err != nil {
return nil, errors.Wrapf(err, "error configuring runtime")
}
}
// Set up containers/storage
store, err := storage.GetStore(runtime.config.StorageConfig)
if err != nil {
return nil, err
}
runtime.store = store
// Set up containers/image
runtime.imageContext = &types.SystemContext{
SignaturePolicyPath: runtime.config.SignaturePolicyPath,
}
runtime.seccompEnabled = seccomp.IsEnabled()
runtime.apparmorEnabled = apparmor.IsEnabled()
// Mark the runtime as valid - ready to be used, cannot be modified
// further
runtime.valid = true
return runtime, nil
}
// GetConfig returns a copy of the configuration used by the runtime
func (r *Runtime) GetConfig() *RuntimeConfig {
r.lock.RLock()
defer r.lock.RUnlock()
if !r.valid {
return nil
}
config := new(RuntimeConfig)
// Copy so the caller won't be able to modify the actual config
deepcopier.Copy(r.config).To(config)
return config
}
// Shutdown shuts down the runtime and associated containers and storage
// If force is true, containers and mounted storage will be shut down before
// cleaning up; if force is false, an error will be returned if there are
// still containers running or mounted
func (r *Runtime) Shutdown(force bool) error {
r.lock.Lock()
defer r.lock.Unlock()
if !r.valid {
return fmt.Errorf("runtime has already been shut down")
}
_, err := r.store.Shutdown(force)
return err
} }
// Container API // Container API

22
vendor/github.com/ulule/deepcopier/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Ulule
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.

129
vendor/github.com/ulule/deepcopier/README.md generated vendored Normal file
View file

@ -0,0 +1,129 @@
# Deepcopier
[![Build Status](https://secure.travis-ci.org/ulule/deepcopier.png?branch=master)](http://travis-ci.org/ulule/deepcopier)
This package is meant to make copying of structs to/from others structs a bit easier.
## Installation
```bash
go get -u github.com/ulule/deepcopier
```
## Usage
```golang
// Deep copy instance1 into instance2
Copy(instance1).To(instance2)
// Deep copy instance1 into instance2 and passes the following context (which
// is basically a map[string]interface{}) as first argument
// to methods of instance2 that defined the struct tag "context".
Copy(instance1).WithContext(map[string]interface{}{"foo": "bar"}).To(instance2)
// Deep copy instance2 into instance1
Copy(instance1).From(instance2)
// Deep copy instance2 into instance1 and passes the following context (which
// is basically a map[string]interface{}) as first argument
// to methods of instance1 that defined the struct tag "context".
Copy(instance1).WithContext(map[string]interface{}{"foo": "bar"}).From(instance2)
```
Available options for `deepcopier` struct tag:
| Option | Description |
| --------- | -------------------------------------------------------------------- |
| `field` | Field or method name in source instance |
| `skip` | Ignores the field |
| `context` | Takes a `map[string]interface{}` as first argument (for methods) |
| `force` | Set the value of a `sql.Null*` field (instead of copying the struct) |
**Options example:**
```golang
type Source struct {
Name string
SkipMe string
SQLNullStringToSQLNullString sql.NullString
SQLNullStringToString sql.NullString
}
func (Source) MethodThatTakesContext(c map[string]interface{}) string {
return "whatever"
}
type Destination struct {
FieldWithAnotherNameInSource string `deepcopier:"field:Name"`
SkipMe string `deepcopier:"skip"`
MethodThatTakesContext string `deepcopier:"context"`
SQLNullStringToSQLNullString sql.NullString
SQLNullStringToString string `deepcopier:"force"`
}
```
Example:
```golang
package main
import (
"fmt"
"github.com/ulule/deepcopier"
)
// Model
type User struct {
// Basic string field
Name string
// Deepcopier supports https://golang.org/pkg/database/sql/driver/#Valuer
Email sql.NullString
}
func (u *User) MethodThatTakesContext(ctx map[string]interface{}) string {
// do whatever you want
return "hello from this method"
}
// Resource
type UserResource struct {
DisplayName string `deepcopier:"field:Name"`
SkipMe string `deepcopier:"skip"`
MethodThatTakesContext string `deepcopier:"context"`
Email string `deepcopier:"force"`
}
func main() {
user := &User{
Name: "gilles",
Email: sql.NullString{
Valid: true,
String: "gilles@example.com",
},
}
resource := &UserResource{}
deepcopier.Copy(user).To(resource)
fmt.Println(resource.DisplayName)
fmt.Println(resource.Email)
}
```
Looking for more information about the usage?
We wrote [an introduction article](https://github.com/ulule/deepcopier/blob/master/examples/rest-usage/README.rst).
Have a look and feel free to give us your feedback.
## Contributing
* Ping us on twitter [@oibafsellig](https://twitter.com/oibafsellig), [@thoas](https://twitter.com/thoas)
* Fork the [project](https://github.com/ulule/deepcopier)
* Help us improving and fixing [issues](https://github.com/ulule/deepcopier/issues)
Don't hesitate ;)

350
vendor/github.com/ulule/deepcopier/deepcopier.go generated vendored Normal file
View file

@ -0,0 +1,350 @@
package deepcopier
import (
"database/sql/driver"
"fmt"
"reflect"
"strings"
)
const (
// TagName is the deepcopier struct tag name.
TagName = "deepcopier"
// FieldOptionName is the from field option name for struct tag.
FieldOptionName = "field"
// ContextOptionName is the context option name for struct tag.
ContextOptionName = "context"
// SkipOptionName is the skip option name for struct tag.
SkipOptionName = "skip"
// ForceOptionName is the skip option name for struct tag.
ForceOptionName = "force"
)
type (
// TagOptions is a map that contains extracted struct tag options.
TagOptions map[string]string
// Options are copier options.
Options struct {
// Context given to WithContext() method.
Context map[string]interface{}
// Reversed reverses struct tag checkings.
Reversed bool
}
)
// DeepCopier deep copies a struct to/from a struct.
type DeepCopier struct {
dst interface{}
src interface{}
ctx map[string]interface{}
}
// Copy sets source or destination.
func Copy(src interface{}) *DeepCopier {
return &DeepCopier{src: src}
}
// WithContext injects the given context into the builder instance.
func (dc *DeepCopier) WithContext(ctx map[string]interface{}) *DeepCopier {
dc.ctx = ctx
return dc
}
// To sets the destination.
func (dc *DeepCopier) To(dst interface{}) error {
dc.dst = dst
return process(dc.dst, dc.src, Options{Context: dc.ctx})
}
// From sets the given the source as destination and destination as source.
func (dc *DeepCopier) From(src interface{}) error {
dc.dst = dc.src
dc.src = src
return process(dc.dst, dc.src, Options{Context: dc.ctx, Reversed: true})
}
// process handles copy.
func process(dst interface{}, src interface{}, args ...Options) error {
var (
options = Options{}
srcValue = reflect.Indirect(reflect.ValueOf(src))
dstValue = reflect.Indirect(reflect.ValueOf(dst))
srcFieldNames = getFieldNames(src)
srcMethodNames = getMethodNames(src)
)
if len(args) > 0 {
options = args[0]
}
if !dstValue.CanAddr() {
return fmt.Errorf("destination %+v is unaddressable", dstValue.Interface())
}
for _, f := range srcFieldNames {
var (
srcFieldValue = srcValue.FieldByName(f)
srcFieldType, srcFieldFound = srcValue.Type().FieldByName(f)
srcFieldName = srcFieldType.Name
dstFieldName = srcFieldName
tagOptions TagOptions
)
if !srcFieldFound {
continue
}
if options.Reversed {
tagOptions = getTagOptions(srcFieldType.Tag.Get(TagName))
if v, ok := tagOptions[FieldOptionName]; ok && v != "" {
dstFieldName = v
}
} else {
if name, opts := getRelatedField(dst, srcFieldName); name != "" {
dstFieldName, tagOptions = name, opts
}
}
if _, ok := tagOptions[SkipOptionName]; ok {
continue
}
var (
dstFieldType, dstFieldFound = dstValue.Type().FieldByName(dstFieldName)
dstFieldValue = dstValue.FieldByName(dstFieldName)
)
if !dstFieldFound {
continue
}
// Force option for empty interfaces and nullable types
_, force := tagOptions[ForceOptionName]
// Valuer -> ptr
if isNullableType(srcFieldType.Type) && dstFieldValue.Kind() == reflect.Ptr && force {
v, _ := srcFieldValue.Interface().(driver.Valuer).Value()
if v == nil {
continue
}
valueType := reflect.TypeOf(v)
ptr := reflect.New(valueType)
ptr.Elem().Set(reflect.ValueOf(v))
if valueType.AssignableTo(dstFieldType.Type.Elem()) {
dstFieldValue.Set(ptr)
}
continue
}
// Valuer -> value
if isNullableType(srcFieldType.Type) {
if force {
v, _ := srcFieldValue.Interface().(driver.Valuer).Value()
if v == nil {
continue
}
rv := reflect.ValueOf(v)
if rv.Type().AssignableTo(dstFieldType.Type) {
dstFieldValue.Set(rv)
}
}
continue
}
if dstFieldValue.Kind() == reflect.Interface {
if force {
dstFieldValue.Set(srcFieldValue)
}
continue
}
// Ptr -> Value
if srcFieldType.Type.Kind() == reflect.Ptr && !srcFieldValue.IsNil() && dstFieldType.Type.Kind() != reflect.Ptr {
indirect := reflect.Indirect(srcFieldValue)
if indirect.Type().AssignableTo(dstFieldType.Type) {
dstFieldValue.Set(indirect)
continue
}
}
// Other types
if srcFieldType.Type.AssignableTo(dstFieldType.Type) {
dstFieldValue.Set(srcFieldValue)
}
}
for _, m := range srcMethodNames {
name, opts := getRelatedField(dst, m)
if name == "" {
continue
}
if _, ok := opts[SkipOptionName]; ok {
continue
}
method := reflect.ValueOf(src).MethodByName(m)
if !method.IsValid() {
return fmt.Errorf("method %s is invalid", m)
}
var (
dstFieldType, _ = dstValue.Type().FieldByName(name)
dstFieldValue = dstValue.FieldByName(name)
_, withContext = opts[ContextOptionName]
_, force = opts[ForceOptionName]
)
args := []reflect.Value{}
if withContext {
args = []reflect.Value{reflect.ValueOf(options.Context)}
}
var (
result = method.Call(args)[0]
resultInterface = result.Interface()
resultValue = reflect.ValueOf(resultInterface)
resultType = resultValue.Type()
)
// Value -> Ptr
if dstFieldValue.Kind() == reflect.Ptr && force {
ptr := reflect.New(resultType)
ptr.Elem().Set(resultValue)
if ptr.Type().AssignableTo(dstFieldType.Type) {
dstFieldValue.Set(ptr)
}
continue
}
// Ptr -> value
if resultValue.Kind() == reflect.Ptr && force {
if resultValue.Elem().Type().AssignableTo(dstFieldType.Type) {
dstFieldValue.Set(resultValue.Elem())
}
continue
}
if resultType.AssignableTo(dstFieldType.Type) && result.IsValid() {
dstFieldValue.Set(result)
}
}
return nil
}
// getTagOptions parses deepcopier tag field and returns options.
func getTagOptions(value string) TagOptions {
options := TagOptions{}
for _, opt := range strings.Split(value, ";") {
o := strings.Split(opt, ":")
// deepcopier:"keyword; without; value;"
if len(o) == 1 {
options[o[0]] = ""
}
// deepcopier:"key:value; anotherkey:anothervalue"
if len(o) == 2 {
options[strings.TrimSpace(o[0])] = strings.TrimSpace(o[1])
}
}
return options
}
// getRelatedField returns first matching field.
func getRelatedField(instance interface{}, name string) (string, TagOptions) {
var (
value = reflect.Indirect(reflect.ValueOf(instance))
fieldName string
tagOptions TagOptions
)
for i := 0; i < value.NumField(); i++ {
var (
vField = value.Field(i)
tField = value.Type().Field(i)
tagOptions = getTagOptions(tField.Tag.Get(TagName))
)
if tField.Type.Kind() == reflect.Struct && tField.Anonymous {
if n, o := getRelatedField(vField.Interface(), name); n != "" {
return n, o
}
}
if v, ok := tagOptions[FieldOptionName]; ok && v == name {
return tField.Name, tagOptions
}
if tField.Name == name {
return tField.Name, tagOptions
}
}
return fieldName, tagOptions
}
// getMethodNames returns instance's method names.
func getMethodNames(instance interface{}) []string {
var methods []string
t := reflect.TypeOf(instance)
for i := 0; i < t.NumMethod(); i++ {
methods = append(methods, t.Method(i).Name)
}
return methods
}
// getFieldNames returns instance's field names.
func getFieldNames(instance interface{}) []string {
var (
fields []string
v = reflect.Indirect(reflect.ValueOf(instance))
t = v.Type()
)
if t.Kind() != reflect.Struct {
return nil
}
for i := 0; i < v.NumField(); i++ {
var (
vField = v.Field(i)
tField = v.Type().Field(i)
)
// Is exportable?
if tField.PkgPath != "" {
continue
}
if tField.Type.Kind() == reflect.Struct && tField.Anonymous {
fields = append(fields, getFieldNames(vField.Interface())...)
continue
}
fields = append(fields, tField.Name)
}
return fields
}
// isNullableType returns true if the given type is a nullable one.
func isNullableType(t reflect.Type) bool {
return t.ConvertibleTo(reflect.TypeOf((*driver.Valuer)(nil)).Elem())
}