Add basic skeleton of libpod runtime
Signed-off-by: Matthew Heon <mheon@redhat.com>
This commit is contained in:
parent
3473e8afed
commit
2a121111b5
5 changed files with 715 additions and 28 deletions
|
@ -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
|
||||||
|
|
|
@ -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
22
vendor/github.com/ulule/deepcopier/LICENSE
generated
vendored
Normal 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
129
vendor/github.com/ulule/deepcopier/README.md
generated
vendored
Normal 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
350
vendor/github.com/ulule/deepcopier/deepcopier.go
generated
vendored
Normal 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())
|
||||||
|
}
|
Loading…
Reference in a new issue