From 2a121111b511159dbb06349d894818ff79b47c1f Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 28 Aug 2017 17:33:02 -0400 Subject: [PATCH] Add basic skeleton of libpod runtime Signed-off-by: Matthew Heon --- libpod/options.go | 125 +++++-- libpod/runtime.go | 117 +++++- vendor/github.com/ulule/deepcopier/LICENSE | 22 ++ vendor/github.com/ulule/deepcopier/README.md | 129 +++++++ .../github.com/ulule/deepcopier/deepcopier.go | 350 ++++++++++++++++++ 5 files changed, 715 insertions(+), 28 deletions(-) create mode 100644 vendor/github.com/ulule/deepcopier/LICENSE create mode 100644 vendor/github.com/ulule/deepcopier/README.md create mode 100644 vendor/github.com/ulule/deepcopier/deepcopier.go diff --git a/libpod/options.go b/libpod/options.go index cbc20cba..ed259e55 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -4,15 +4,14 @@ import ( "fmt" "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/pod" ) var ( - runtimeNotImplemented = func(rt *Runtime) error { - return fmt.Errorf("NOT IMPLEMENTED") - } - ctrNotImplemented = func(c *ctr.Container) error { + errRuntimeFinalized = fmt.Errorf("runtime has already been finalized") + ctrNotImplemented = func(c *ctr.Container) error { return fmt.Errorf("NOT IMPLEMENTED") } ) @@ -37,15 +36,48 @@ const ( // WithStorageConfig uses the given configuration to set up container storage // If this is not specified, the system default configuration will be used // instead -func WithStorageConfig(config *storage.StoreOptions) RuntimeOption { - return runtimeNotImplemented +func WithStorageConfig(config storage.StoreOptions) RuntimeOption { + 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 // If this is not specified, the system default configuration will be used // instead 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 @@ -53,52 +85,97 @@ func WithImageConfig(defaultTransport string, insecureRegistries, registries []s // If this is not specified, the system default configuration will be used // instead 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 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 // runtime 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 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 // handle cgroups for containers 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 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 -// the default for created containers -func WithApparmorProfile(profile string) RuntimeOption { - return runtimeNotImplemented -} + rt.config.SelinuxEnabled = true -// WithSeccompProfile specifies the seccomp profile which will be used as the -// default for created containers -func WithSeccompProfile(profilePath string) RuntimeOption { - return runtimeNotImplemented + return nil + } } // WithPidsLimit specifies the maximum number of processes each container is // restricted to 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 diff --git a/libpod/runtime.go b/libpod/runtime.go index 8e7125e5..f2c4c7c5 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -1,26 +1,135 @@ package libpod import ( + "fmt" + "sync" + + "github.com/containers/image/types" "github.com/containers/storage" "github.com/kubernetes-incubator/cri-o/libpod/ctr" "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" + "github.com/pkg/errors" + "github.com/ulule/deepcopier" ) -// Runtime API - // A RuntimeOption is a functional option which alters the Runtime created by // NewRuntime type RuntimeOption func(*Runtime) error // Runtime is the core libpod runtime 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 +// Options can be passed to override the default configuration for the runtime 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 diff --git a/vendor/github.com/ulule/deepcopier/LICENSE b/vendor/github.com/ulule/deepcopier/LICENSE new file mode 100644 index 00000000..d5c4ea02 --- /dev/null +++ b/vendor/github.com/ulule/deepcopier/LICENSE @@ -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. + diff --git a/vendor/github.com/ulule/deepcopier/README.md b/vendor/github.com/ulule/deepcopier/README.md new file mode 100644 index 00000000..25aafae8 --- /dev/null +++ b/vendor/github.com/ulule/deepcopier/README.md @@ -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 ;) diff --git a/vendor/github.com/ulule/deepcopier/deepcopier.go b/vendor/github.com/ulule/deepcopier/deepcopier.go new file mode 100644 index 00000000..8cccb5b7 --- /dev/null +++ b/vendor/github.com/ulule/deepcopier/deepcopier.go @@ -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()) +}