mirror of https://github.com/k3d-io/k3d
[FEATURE] add ability to add ports to an existing loadbalancer (#615)
parent
1deb0aa64d
commit
897e49a8ee
@ -0,0 +1,113 @@ |
|||||||
|
/* |
||||||
|
Copyright © 2020-2021 The k3d Author(s) |
||||||
|
|
||||||
|
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. |
||||||
|
*/ |
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/docker/go-connections/nat" |
||||||
|
"github.com/rancher/k3d/v4/cmd/util" |
||||||
|
"github.com/rancher/k3d/v4/pkg/client" |
||||||
|
"github.com/rancher/k3d/v4/pkg/runtimes" |
||||||
|
k3d "github.com/rancher/k3d/v4/pkg/types" |
||||||
|
log "github.com/sirupsen/logrus" |
||||||
|
"github.com/spf13/cobra" |
||||||
|
) |
||||||
|
|
||||||
|
// NewCmdNodeEdit returns a new cobra command
|
||||||
|
func NewCmdNodeEdit() *cobra.Command { |
||||||
|
|
||||||
|
// create new cobra command
|
||||||
|
cmd := &cobra.Command{ |
||||||
|
Use: "edit NAME", |
||||||
|
Short: "[EXPERIMENTAL] Edit node(s).", |
||||||
|
Long: `[EXPERIMENTAL] Edit node(s).`, |
||||||
|
Args: cobra.ExactArgs(1), |
||||||
|
Aliases: []string{"update"}, |
||||||
|
ValidArgsFunction: util.ValidArgsAvailableNodes, |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
|
||||||
|
existingNode, changeset := parseEditNodeCmd(cmd, args) |
||||||
|
|
||||||
|
log.Debugf("===== Current =====\n%+v\n===== Changeset =====\n%+v\n", existingNode, changeset) |
||||||
|
|
||||||
|
if err := client.NodeEdit(cmd.Context(), runtimes.SelectedRuntime, existingNode, changeset); err != nil { |
||||||
|
log.Fatalln(err) |
||||||
|
} |
||||||
|
|
||||||
|
log.Infof("Successfully updated %s", existingNode.Name) |
||||||
|
|
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
// add subcommands
|
||||||
|
|
||||||
|
// add flags
|
||||||
|
cmd.Flags().StringArray("port-add", nil, "[EXPERIMENTAL] (serverlb only!) Map ports from the node container to the host (Format: `[HOST:][HOSTPORT:]CONTAINERPORT[/PROTOCOL][@NODEFILTER]`)\n - Example: `k3d node edit k3d-mycluster-serverlb --port-add 8080:80`") |
||||||
|
|
||||||
|
// done
|
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// parseEditNodeCmd parses the command input into variables required to delete nodes
|
||||||
|
func parseEditNodeCmd(cmd *cobra.Command, args []string) (*k3d.Node, *k3d.Node) { |
||||||
|
|
||||||
|
existingNode, err := client.NodeGet(cmd.Context(), runtimes.SelectedRuntime, &k3d.Node{Name: args[0]}) |
||||||
|
if err != nil { |
||||||
|
log.Fatalln(err) |
||||||
|
} |
||||||
|
|
||||||
|
if existingNode == nil { |
||||||
|
log.Infof("Node %s not found", args[0]) |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
if existingNode.Role != k3d.LoadBalancerRole { |
||||||
|
log.Fatalln("Currently only the loadbalancer can be updated!") |
||||||
|
} |
||||||
|
|
||||||
|
changeset := &k3d.Node{} |
||||||
|
|
||||||
|
/* |
||||||
|
* --port-add |
||||||
|
*/ |
||||||
|
portFlags, err := cmd.Flags().GetStringArray("port-add") |
||||||
|
if err != nil { |
||||||
|
log.Errorln(err) |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
// init portmap
|
||||||
|
changeset.Ports = nat.PortMap{} |
||||||
|
|
||||||
|
for _, flag := range portFlags { |
||||||
|
|
||||||
|
portmappings, err := nat.ParsePortSpec(flag) |
||||||
|
if err != nil { |
||||||
|
log.Fatalf("Failed to parse port spec '%s': %+v", flag, err) |
||||||
|
} |
||||||
|
|
||||||
|
for _, pm := range portmappings { |
||||||
|
changeset.Ports[pm.Port] = append(changeset.Ports[pm.Port], pm.Binding) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return existingNode, changeset |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
CURR_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" |
||||||
|
[ -d "$CURR_DIR" ] || { echo "FATAL: no current dir (maybe running in zsh?)"; exit 1; } |
||||||
|
|
||||||
|
# shellcheck source=./common.sh |
||||||
|
source "$CURR_DIR/common.sh" |
||||||
|
|
||||||
|
export CURRENT_STAGE="Test | NodeEdit" |
||||||
|
|
||||||
|
highlight "[START] NodeEdit" |
||||||
|
|
||||||
|
clustername="test-node-edit" |
||||||
|
|
||||||
|
existingPortMappingHostPort="1111" |
||||||
|
existingPortMappingContainerPort="2222" |
||||||
|
newPortMappingHostPort="3333" |
||||||
|
newPortMappingContainerPort="4444" |
||||||
|
|
||||||
|
info "Creating cluster $clustername..." |
||||||
|
$EXE cluster create $clustername --port "$existingPortMappingHostPort:$existingPortMappingContainerPort@loadbalancer" || failed "could not create cluster $clustername" |
||||||
|
|
||||||
|
info "Checking cluster access..." |
||||||
|
check_clusters "$clustername" || failed "error checking cluster access" |
||||||
|
|
||||||
|
info "Adding port-mapping to loadbalancer..." |
||||||
|
$EXE node edit k3d-$clustername-serverlb --port-add $existingPortMappingHostPort:$existingPortMappingContainerPort --port-add $newPortMappingHostPort:$newPortMappingContainerPort || failed "failed to add port-mapping to serverlb in $clustername" |
||||||
|
|
||||||
|
info "Checking port-mappings..." |
||||||
|
docker inspect k3d-$clustername-serverlb --format '{{ range $k, $v := .NetworkSettings.Ports }}{{ printf "%s->%s\n" $k $v }}{{ end }}' | grep -E "^$existingPortMappingContainerPort" || failed "failed to verify pre-existing port-mapping" |
||||||
|
docker inspect k3d-$clustername-serverlb --format '{{ range $k, $v := .NetworkSettings.Ports }}{{ printf "%s->%s\n" $k $v }}{{ end }}' | grep -E "^$newPortMappingContainerPort" || failed "failed to verify pre-existing port-mapping" |
||||||
|
|
||||||
|
info "Checking cluster access..." |
||||||
|
check_clusters "$clustername" || failed "error checking cluster access" |
||||||
|
|
||||||
|
info "Deleting cluster $clustername..." |
||||||
|
$EXE cluster delete $clustername || failed "failed to delete the cluster $clustername" |
||||||
|
|
||||||
|
exit 0 |
@ -0,0 +1,21 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2014 Mitchell Hashimoto |
||||||
|
|
||||||
|
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,21 @@ |
|||||||
|
# copystructure |
||||||
|
|
||||||
|
copystructure is a Go library for deep copying values in Go. |
||||||
|
|
||||||
|
This allows you to copy Go values that may contain reference values |
||||||
|
such as maps, slices, or pointers, and copy their data as well instead |
||||||
|
of just their references. |
||||||
|
|
||||||
|
## Installation |
||||||
|
|
||||||
|
Standard `go get`: |
||||||
|
|
||||||
|
``` |
||||||
|
$ go get github.com/mitchellh/copystructure |
||||||
|
``` |
||||||
|
|
||||||
|
## Usage & Example |
||||||
|
|
||||||
|
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/copystructure). |
||||||
|
|
||||||
|
The `Copy` function has examples associated with it there. |
@ -0,0 +1,15 @@ |
|||||||
|
package copystructure |
||||||
|
|
||||||
|
import ( |
||||||
|
"reflect" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
Copiers[reflect.TypeOf(time.Time{})] = timeCopier |
||||||
|
} |
||||||
|
|
||||||
|
func timeCopier(v interface{}) (interface{}, error) { |
||||||
|
// Just... copy it.
|
||||||
|
return v.(time.Time), nil |
||||||
|
} |
@ -0,0 +1,631 @@ |
|||||||
|
package copystructure |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"reflect" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/mitchellh/reflectwalk" |
||||||
|
) |
||||||
|
|
||||||
|
const tagKey = "copy" |
||||||
|
|
||||||
|
// Copy returns a deep copy of v.
|
||||||
|
//
|
||||||
|
// Copy is unable to copy unexported fields in a struct (lowercase field names).
|
||||||
|
// Unexported fields can't be reflected by the Go runtime and therefore
|
||||||
|
// copystructure can't perform any data copies.
|
||||||
|
//
|
||||||
|
// For structs, copy behavior can be controlled with struct tags. For example:
|
||||||
|
//
|
||||||
|
// struct {
|
||||||
|
// Name string
|
||||||
|
// Data *bytes.Buffer `copy:"shallow"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The available tag values are:
|
||||||
|
//
|
||||||
|
// * "ignore" - The field will be ignored, effectively resulting in it being
|
||||||
|
// assigned the zero value in the copy.
|
||||||
|
//
|
||||||
|
// * "shallow" - The field will be be shallow copied. This means that references
|
||||||
|
// values such as pointers, maps, slices, etc. will be directly assigned
|
||||||
|
// versus deep copied.
|
||||||
|
//
|
||||||
|
func Copy(v interface{}) (interface{}, error) { |
||||||
|
return Config{}.Copy(v) |
||||||
|
} |
||||||
|
|
||||||
|
// CopierFunc is a function that knows how to deep copy a specific type.
|
||||||
|
// Register these globally with the Copiers variable.
|
||||||
|
type CopierFunc func(interface{}) (interface{}, error) |
||||||
|
|
||||||
|
// Copiers is a map of types that behave specially when they are copied.
|
||||||
|
// If a type is found in this map while deep copying, this function
|
||||||
|
// will be called to copy it instead of attempting to copy all fields.
|
||||||
|
//
|
||||||
|
// The key should be the type, obtained using: reflect.TypeOf(value with type).
|
||||||
|
//
|
||||||
|
// It is unsafe to write to this map after Copies have started. If you
|
||||||
|
// are writing to this map while also copying, wrap all modifications to
|
||||||
|
// this map as well as to Copy in a mutex.
|
||||||
|
var Copiers map[reflect.Type]CopierFunc = make(map[reflect.Type]CopierFunc) |
||||||
|
|
||||||
|
// ShallowCopiers is a map of pointer types that behave specially
|
||||||
|
// when they are copied. If a type is found in this map while deep
|
||||||
|
// copying, the pointer value will be shallow copied and not walked
|
||||||
|
// into.
|
||||||
|
//
|
||||||
|
// The key should be the type, obtained using: reflect.TypeOf(value
|
||||||
|
// with type).
|
||||||
|
//
|
||||||
|
// It is unsafe to write to this map after Copies have started. If you
|
||||||
|
// are writing to this map while also copying, wrap all modifications to
|
||||||
|
// this map as well as to Copy in a mutex.
|
||||||
|
var ShallowCopiers map[reflect.Type]struct{} = make(map[reflect.Type]struct{}) |
||||||
|
|
||||||
|
// Must is a helper that wraps a call to a function returning
|
||||||
|
// (interface{}, error) and panics if the error is non-nil. It is intended
|
||||||
|
// for use in variable initializations and should only be used when a copy
|
||||||
|
// error should be a crashing case.
|
||||||
|
func Must(v interface{}, err error) interface{} { |
||||||
|
if err != nil { |
||||||
|
panic("copy error: " + err.Error()) |
||||||
|
} |
||||||
|
|
||||||
|
return v |
||||||
|
} |
||||||
|
|
||||||
|
var errPointerRequired = errors.New("Copy argument must be a pointer when Lock is true") |
||||||
|
|
||||||
|
type Config struct { |
||||||
|
// Lock any types that are a sync.Locker and are not a mutex while copying.
|
||||||
|
// If there is an RLocker method, use that to get the sync.Locker.
|
||||||
|
Lock bool |
||||||
|
|
||||||
|
// Copiers is a map of types associated with a CopierFunc. Use the global
|
||||||
|
// Copiers map if this is nil.
|
||||||
|
Copiers map[reflect.Type]CopierFunc |
||||||
|
|
||||||
|
// ShallowCopiers is a map of pointer types that when they are
|
||||||
|
// shallow copied no matter where they are encountered. Use the
|
||||||
|
// global ShallowCopiers if this is nil.
|
||||||
|
ShallowCopiers map[reflect.Type]struct{} |
||||||
|
} |
||||||
|
|
||||||
|
func (c Config) Copy(v interface{}) (interface{}, error) { |
||||||
|
if c.Lock && reflect.ValueOf(v).Kind() != reflect.Ptr { |
||||||
|
return nil, errPointerRequired |
||||||
|
} |
||||||
|
|
||||||
|
w := new(walker) |
||||||
|
if c.Lock { |
||||||
|
w.useLocks = true |
||||||
|
} |
||||||
|
|
||||||
|
if c.Copiers == nil { |
||||||
|
c.Copiers = Copiers |
||||||
|
} |
||||||
|
w.copiers = c.Copiers |
||||||
|
|
||||||
|
if c.ShallowCopiers == nil { |
||||||
|
c.ShallowCopiers = ShallowCopiers |
||||||
|
} |
||||||
|
w.shallowCopiers = c.ShallowCopiers |
||||||
|
|
||||||
|
err := reflectwalk.Walk(v, w) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Get the result. If the result is nil, then we want to turn it
|
||||||
|
// into a typed nil if we can.
|
||||||
|
result := w.Result |
||||||
|
if result == nil { |
||||||
|
val := reflect.ValueOf(v) |
||||||
|
result = reflect.Indirect(reflect.New(val.Type())).Interface() |
||||||
|
} |
||||||
|
|
||||||
|
return result, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Return the key used to index interfaces types we've seen. Store the number
|
||||||
|
// of pointers in the upper 32bits, and the depth in the lower 32bits. This is
|
||||||
|
// easy to calculate, easy to match a key with our current depth, and we don't
|
||||||
|
// need to deal with initializing and cleaning up nested maps or slices.
|
||||||
|
func ifaceKey(pointers, depth int) uint64 { |
||||||
|
return uint64(pointers)<<32 | uint64(depth) |
||||||
|
} |
||||||
|
|
||||||
|
type walker struct { |
||||||
|
Result interface{} |
||||||
|
|
||||||
|
copiers map[reflect.Type]CopierFunc |
||||||
|
shallowCopiers map[reflect.Type]struct{} |
||||||
|
depth int |
||||||
|
ignoreDepth int |
||||||
|
vals []reflect.Value |
||||||
|
cs []reflect.Value |
||||||
|
|
||||||
|
// This stores the number of pointers we've walked over, indexed by depth.
|
||||||
|
ps []int |
||||||
|
|
||||||
|
// If an interface is indirected by a pointer, we need to know the type of
|
||||||
|
// interface to create when creating the new value. Store the interface
|
||||||
|
// types here, indexed by both the walk depth and the number of pointers
|
||||||
|
// already seen at that depth. Use ifaceKey to calculate the proper uint64
|
||||||
|
// value.
|
||||||
|
ifaceTypes map[uint64]reflect.Type |
||||||
|
|
||||||
|
// any locks we've taken, indexed by depth
|
||||||
|
locks []sync.Locker |
||||||
|
// take locks while walking the structure
|
||||||
|
useLocks bool |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Enter(l reflectwalk.Location) error { |
||||||
|
w.depth++ |
||||||
|
|
||||||
|
// ensure we have enough elements to index via w.depth
|
||||||
|
for w.depth >= len(w.locks) { |
||||||
|
w.locks = append(w.locks, nil) |
||||||
|
} |
||||||
|
|
||||||
|
for len(w.ps) < w.depth+1 { |
||||||
|
w.ps = append(w.ps, 0) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Exit(l reflectwalk.Location) error { |
||||||
|
locker := w.locks[w.depth] |
||||||
|
w.locks[w.depth] = nil |
||||||
|
if locker != nil { |
||||||
|
defer locker.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
// clear out pointers and interfaces as we exit the stack
|
||||||
|
w.ps[w.depth] = 0 |
||||||
|
|
||||||
|
for k := range w.ifaceTypes { |
||||||
|
mask := uint64(^uint32(0)) |
||||||
|
if k&mask == uint64(w.depth) { |
||||||
|
delete(w.ifaceTypes, k) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
w.depth-- |
||||||
|
if w.ignoreDepth > w.depth { |
||||||
|
w.ignoreDepth = 0 |
||||||
|
} |
||||||
|
|
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
switch l { |
||||||
|
case reflectwalk.Array: |
||||||
|
fallthrough |
||||||
|
case reflectwalk.Map: |
||||||
|
fallthrough |
||||||
|
case reflectwalk.Slice: |
||||||
|
w.replacePointerMaybe() |
||||||
|
|
||||||
|
// Pop map off our container
|
||||||
|
w.cs = w.cs[:len(w.cs)-1] |
||||||
|
case reflectwalk.MapValue: |
||||||
|
// Pop off the key and value
|
||||||
|
mv := w.valPop() |
||||||
|
mk := w.valPop() |
||||||
|
m := w.cs[len(w.cs)-1] |
||||||
|
|
||||||
|
// If mv is the zero value, SetMapIndex deletes the key form the map,
|
||||||
|
// or in this case never adds it. We need to create a properly typed
|
||||||
|
// zero value so that this key can be set.
|
||||||
|
if !mv.IsValid() { |
||||||
|
mv = reflect.Zero(m.Elem().Type().Elem()) |
||||||
|
} |
||||||
|
m.Elem().SetMapIndex(mk, mv) |
||||||
|
case reflectwalk.ArrayElem: |
||||||
|
// Pop off the value and the index and set it on the array
|
||||||
|
v := w.valPop() |
||||||
|
i := w.valPop().Interface().(int) |
||||||
|
if v.IsValid() { |
||||||
|
a := w.cs[len(w.cs)-1] |
||||||
|
ae := a.Elem().Index(i) // storing array as pointer on stack - so need Elem() call
|
||||||
|
if ae.CanSet() { |
||||||
|
ae.Set(v) |
||||||
|
} |
||||||
|
} |
||||||
|
case reflectwalk.SliceElem: |
||||||
|
// Pop off the value and the index and set it on the slice
|
||||||
|
v := w.valPop() |
||||||
|
i := w.valPop().Interface().(int) |
||||||
|
if v.IsValid() { |
||||||
|
s := w.cs[len(w.cs)-1] |
||||||
|
se := s.Elem().Index(i) |
||||||
|
if se.CanSet() { |
||||||
|
se.Set(v) |
||||||
|
} |
||||||
|
} |
||||||
|
case reflectwalk.Struct: |
||||||
|
w.replacePointerMaybe() |
||||||
|
|
||||||
|
// Remove the struct from the container stack
|
||||||
|
w.cs = w.cs[:len(w.cs)-1] |
||||||
|
case reflectwalk.StructField: |
||||||
|
// Pop off the value and the field
|
||||||
|
v := w.valPop() |
||||||
|
f := w.valPop().Interface().(reflect.StructField) |
||||||
|
if v.IsValid() { |
||||||
|
s := w.cs[len(w.cs)-1] |
||||||
|
sf := reflect.Indirect(s).FieldByName(f.Name) |
||||||
|
|
||||||
|
if sf.CanSet() { |
||||||
|
sf.Set(v) |
||||||
|
} |
||||||
|
} |
||||||
|
case reflectwalk.WalkLoc: |
||||||
|
// Clear out the slices for GC
|
||||||
|
w.cs = nil |
||||||
|
w.vals = nil |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Map(m reflect.Value) error { |
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
w.lock(m) |
||||||
|
|
||||||
|
// Create the map. If the map itself is nil, then just make a nil map
|
||||||
|
var newMap reflect.Value |
||||||
|
if m.IsNil() { |
||||||
|
newMap = reflect.New(m.Type()) |
||||||
|
} else { |
||||||
|
newMap = wrapPtr(reflect.MakeMap(m.Type())) |
||||||
|
} |
||||||
|
|
||||||
|
w.cs = append(w.cs, newMap) |
||||||
|
w.valPush(newMap) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) MapElem(m, k, v reflect.Value) error { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) PointerEnter(v bool) error { |
||||||
|
if v { |
||||||
|
w.ps[w.depth]++ |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) PointerExit(v bool) error { |
||||||
|
if v { |
||||||
|
w.ps[w.depth]-- |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Pointer(v reflect.Value) error { |
||||||
|
if _, ok := w.shallowCopiers[v.Type()]; ok { |
||||||
|
// Shallow copy this value. Use the same logic as primitive, then
|
||||||
|
// return skip.
|
||||||
|
if err := w.Primitive(v); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return reflectwalk.SkipEntry |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Interface(v reflect.Value) error { |
||||||
|
if !v.IsValid() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if w.ifaceTypes == nil { |
||||||
|
w.ifaceTypes = make(map[uint64]reflect.Type) |
||||||
|
} |
||||||
|
|
||||||
|
w.ifaceTypes[ifaceKey(w.ps[w.depth], w.depth)] = v.Type() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Primitive(v reflect.Value) error { |
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
w.lock(v) |
||||||
|
|
||||||
|
// IsValid verifies the v is non-zero and CanInterface verifies
|
||||||
|
// that we're allowed to read this value (unexported fields).
|
||||||
|
var newV reflect.Value |
||||||
|
if v.IsValid() && v.CanInterface() { |
||||||
|
newV = reflect.New(v.Type()) |
||||||
|
newV.Elem().Set(v) |
||||||
|
} |
||||||
|
|
||||||
|
w.valPush(newV) |
||||||
|
w.replacePointerMaybe() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Slice(s reflect.Value) error { |
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
w.lock(s) |
||||||
|
|
||||||
|
var newS reflect.Value |
||||||
|
if s.IsNil() { |
||||||
|
newS = reflect.New(s.Type()) |
||||||
|
} else { |
||||||
|
newS = wrapPtr(reflect.MakeSlice(s.Type(), s.Len(), s.Cap())) |
||||||
|
} |
||||||
|
|
||||||
|
w.cs = append(w.cs, newS) |
||||||
|
w.valPush(newS) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) SliceElem(i int, elem reflect.Value) error { |
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// We don't write the slice here because elem might still be
|
||||||
|
// arbitrarily complex. Just record the index and continue on.
|
||||||
|
w.valPush(reflect.ValueOf(i)) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Array(a reflect.Value) error { |
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
w.lock(a) |
||||||
|
|
||||||
|
newA := reflect.New(a.Type()) |
||||||
|
|
||||||
|
w.cs = append(w.cs, newA) |
||||||
|
w.valPush(newA) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) ArrayElem(i int, elem reflect.Value) error { |
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// We don't write the array here because elem might still be
|
||||||
|
// arbitrarily complex. Just record the index and continue on.
|
||||||
|
w.valPush(reflect.ValueOf(i)) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) Struct(s reflect.Value) error { |
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
w.lock(s) |
||||||
|
|
||||||
|
var v reflect.Value |
||||||
|
if c, ok := w.copiers[s.Type()]; ok { |
||||||
|
// We have a Copier for this struct, so we use that copier to
|
||||||
|
// get the copy, and we ignore anything deeper than this.
|
||||||
|
w.ignoreDepth = w.depth |
||||||
|
|
||||||
|
dup, err := c(s.Interface()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// We need to put a pointer to the value on the value stack,
|
||||||
|
// so allocate a new pointer and set it.
|
||||||
|
v = reflect.New(s.Type()) |
||||||
|
reflect.Indirect(v).Set(reflect.ValueOf(dup)) |
||||||
|
} else { |
||||||
|
// No copier, we copy ourselves and allow reflectwalk to guide
|
||||||
|
// us deeper into the structure for copying.
|
||||||
|
v = reflect.New(s.Type()) |
||||||
|
} |
||||||
|
|
||||||
|
// Push the value onto the value stack for setting the struct field,
|
||||||
|
// and add the struct itself to the containers stack in case we walk
|
||||||
|
// deeper so that its own fields can be modified.
|
||||||
|
w.valPush(v) |
||||||
|
w.cs = append(w.cs, v) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) StructField(f reflect.StructField, v reflect.Value) error { |
||||||
|
if w.ignoring() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// If PkgPath is non-empty, this is a private (unexported) field.
|
||||||
|
// We do not set this unexported since the Go runtime doesn't allow us.
|
||||||
|
if f.PkgPath != "" { |
||||||
|
return reflectwalk.SkipEntry |
||||||
|
} |
||||||
|
|
||||||
|
switch f.Tag.Get(tagKey) { |
||||||
|
case "shallow": |
||||||
|
// If we're shallow copying then assign the value directly to the
|
||||||
|
// struct and skip the entry.
|
||||||
|
if v.IsValid() { |
||||||
|
s := w.cs[len(w.cs)-1] |
||||||
|
sf := reflect.Indirect(s).FieldByName(f.Name) |
||||||
|
if sf.CanSet() { |
||||||
|
sf.Set(v) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return reflectwalk.SkipEntry |
||||||
|
|
||||||
|
case "ignore": |
||||||
|
// Do nothing
|
||||||
|
return reflectwalk.SkipEntry |
||||||
|
} |
||||||
|
|
||||||
|
// Push the field onto the stack, we'll handle it when we exit
|
||||||
|
// the struct field in Exit...
|
||||||
|
w.valPush(reflect.ValueOf(f)) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// ignore causes the walker to ignore any more values until we exit this on
|
||||||
|
func (w *walker) ignore() { |
||||||
|
w.ignoreDepth = w.depth |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) ignoring() bool { |
||||||
|
return w.ignoreDepth > 0 && w.depth >= w.ignoreDepth |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) pointerPeek() bool { |
||||||
|
return w.ps[w.depth] > 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) valPop() reflect.Value { |
||||||
|
result := w.vals[len(w.vals)-1] |
||||||
|
w.vals = w.vals[:len(w.vals)-1] |
||||||
|
|
||||||
|
// If we're out of values, that means we popped everything off. In
|
||||||
|
// this case, we reset the result so the next pushed value becomes
|
||||||
|
// the result.
|
||||||
|
if len(w.vals) == 0 { |
||||||
|
w.Result = nil |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) valPush(v reflect.Value) { |
||||||
|
w.vals = append(w.vals, v) |
||||||
|
|
||||||
|
// If we haven't set the result yet, then this is the result since
|
||||||
|
// it is the first (outermost) value we're seeing.
|
||||||
|
if w.Result == nil && v.IsValid() { |
||||||
|
w.Result = v.Interface() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) replacePointerMaybe() { |
||||||
|
// Determine the last pointer value. If it is NOT a pointer, then
|
||||||
|
// we need to push that onto the stack.
|
||||||
|
if !w.pointerPeek() { |
||||||
|
w.valPush(reflect.Indirect(w.valPop())) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
v := w.valPop() |
||||||
|
|
||||||
|
// If the expected type is a pointer to an interface of any depth,
|
||||||
|
// such as *interface{}, **interface{}, etc., then we need to convert
|
||||||
|
// the value "v" from *CONCRETE to *interface{} so types match for
|
||||||
|
// Set.
|
||||||
|
//
|
||||||
|
// Example if v is type *Foo where Foo is a struct, v would become
|
||||||
|
// *interface{} instead. This only happens if we have an interface expectation
|
||||||
|
// at this depth.
|
||||||
|
//
|
||||||
|
// For more info, see GH-16
|
||||||
|
if iType, ok := w.ifaceTypes[ifaceKey(w.ps[w.depth], w.depth)]; ok && iType.Kind() == reflect.Interface { |
||||||
|
y := reflect.New(iType) // Create *interface{}
|
||||||
|
y.Elem().Set(reflect.Indirect(v)) // Assign "Foo" to interface{} (dereferenced)
|
||||||
|
v = y // v is now typed *interface{} (where *v = Foo)
|
||||||
|
} |
||||||
|
|
||||||
|
for i := 1; i < w.ps[w.depth]; i++ { |
||||||
|
if iType, ok := w.ifaceTypes[ifaceKey(w.ps[w.depth]-i, w.depth)]; ok { |
||||||
|
iface := reflect.New(iType).Elem() |
||||||
|
iface.Set(v) |
||||||
|
v = iface |
||||||
|
} |
||||||
|
|
||||||
|
p := reflect.New(v.Type()) |
||||||
|
p.Elem().Set(v) |
||||||
|
v = p |
||||||
|
} |
||||||
|
|
||||||
|
w.valPush(v) |
||||||
|
} |
||||||
|
|
||||||
|
// if this value is a Locker, lock it and add it to the locks slice
|
||||||
|
func (w *walker) lock(v reflect.Value) { |
||||||
|
if !w.useLocks { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if !v.IsValid() || !v.CanInterface() { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
type rlocker interface { |
||||||
|
RLocker() sync.Locker |
||||||
|
} |
||||||
|
|
||||||
|
var locker sync.Locker |
||||||
|
|
||||||
|
// We can't call Interface() on a value directly, since that requires
|
||||||
|
// a copy. This is OK, since the pointer to a value which is a sync.Locker
|
||||||
|
// is also a sync.Locker.
|
||||||
|
if v.Kind() == reflect.Ptr { |
||||||
|
switch l := v.Interface().(type) { |
||||||
|
case rlocker: |
||||||
|
// don't lock a mutex directly
|
||||||
|
if _, ok := l.(*sync.RWMutex); !ok { |
||||||
|
locker = l.RLocker() |
||||||
|
} |
||||||
|
case sync.Locker: |
||||||
|
locker = l |
||||||
|
} |
||||||
|
} else if v.CanAddr() { |
||||||
|
switch l := v.Addr().Interface().(type) { |
||||||
|
case rlocker: |
||||||
|
// don't lock a mutex directly
|
||||||
|
if _, ok := l.(*sync.RWMutex); !ok { |
||||||
|
locker = l.RLocker() |
||||||
|
} |
||||||
|
case sync.Locker: |
||||||
|
locker = l |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// still no callable locker
|
||||||
|
if locker == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// don't lock a mutex directly
|
||||||
|
switch locker.(type) { |
||||||
|
case *sync.Mutex, *sync.RWMutex: |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
locker.Lock() |
||||||
|
w.locks[w.depth] = locker |
||||||
|
} |
||||||
|
|
||||||
|
// wrapPtr is a helper that takes v and always make it *v. copystructure
|
||||||
|
// stores things internally as pointers until the last moment before unwrapping
|
||||||
|
func wrapPtr(v reflect.Value) reflect.Value { |
||||||
|
if !v.IsValid() { |
||||||
|
return v |
||||||
|
} |
||||||
|
vPtr := reflect.New(v.Type()) |
||||||
|
vPtr.Elem().Set(v) |
||||||
|
return vPtr |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
module github.com/mitchellh/copystructure |
||||||
|
|
||||||
|
go 1.15 |
||||||
|
|
||||||
|
require github.com/mitchellh/reflectwalk v1.0.2 |
@ -0,0 +1,2 @@ |
|||||||
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= |
||||||
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= |
@ -0,0 +1 @@ |
|||||||
|
language: go |
@ -0,0 +1,21 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2013 Mitchell Hashimoto |
||||||
|
|
||||||
|
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,6 @@ |
|||||||
|
# reflectwalk |
||||||
|
|
||||||
|
reflectwalk is a Go library for "walking" a value in Go using reflection, |
||||||
|
in the same way a directory tree can be "walked" on the filesystem. Walking |
||||||
|
a complex structure can allow you to do manipulations on unknown structures |
||||||
|
such as those decoded from JSON. |
@ -0,0 +1 @@ |
|||||||
|
module github.com/mitchellh/reflectwalk |
@ -0,0 +1,19 @@ |
|||||||
|
package reflectwalk |
||||||
|
|
||||||
|
//go:generate stringer -type=Location location.go
|
||||||
|
|
||||||
|
type Location uint |
||||||
|
|
||||||
|
const ( |
||||||
|
None Location = iota |
||||||
|
Map |
||||||
|
MapKey |
||||||
|
MapValue |
||||||
|
Slice |
||||||
|
SliceElem |
||||||
|
Array |
||||||
|
ArrayElem |
||||||
|
Struct |
||||||
|
StructField |
||||||
|
WalkLoc |
||||||
|
) |
@ -0,0 +1,16 @@ |
|||||||
|
// Code generated by "stringer -type=Location location.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package reflectwalk |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
const _Location_name = "NoneMapMapKeyMapValueSliceSliceElemArrayArrayElemStructStructFieldWalkLoc" |
||||||
|
|
||||||
|
var _Location_index = [...]uint8{0, 4, 7, 13, 21, 26, 35, 40, 49, 55, 66, 73} |
||||||
|
|
||||||
|
func (i Location) String() string { |
||||||
|
if i >= Location(len(_Location_index)-1) { |
||||||
|
return fmt.Sprintf("Location(%d)", i) |
||||||
|
} |
||||||
|
return _Location_name[_Location_index[i]:_Location_index[i+1]] |
||||||
|
} |
@ -0,0 +1,420 @@ |
|||||||
|
// reflectwalk is a package that allows you to "walk" complex structures
|
||||||
|
// similar to how you may "walk" a filesystem: visiting every element one
|
||||||
|
// by one and calling callback functions allowing you to handle and manipulate
|
||||||
|
// those elements.
|
||||||
|
package reflectwalk |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"reflect" |
||||||
|
) |
||||||
|
|
||||||
|
// PrimitiveWalker implementations are able to handle primitive values
|
||||||
|
// within complex structures. Primitive values are numbers, strings,
|
||||||
|
// booleans, funcs, chans.
|
||||||
|
//
|
||||||
|
// These primitive values are often members of more complex
|
||||||
|
// structures (slices, maps, etc.) that are walkable by other interfaces.
|
||||||
|
type PrimitiveWalker interface { |
||||||
|
Primitive(reflect.Value) error |
||||||
|
} |
||||||
|
|
||||||
|
// InterfaceWalker implementations are able to handle interface values as they
|
||||||
|
// are encountered during the walk.
|
||||||
|
type InterfaceWalker interface { |
||||||
|
Interface(reflect.Value) error |
||||||
|
} |
||||||
|
|
||||||
|
// MapWalker implementations are able to handle individual elements
|
||||||
|
// found within a map structure.
|
||||||
|
type MapWalker interface { |
||||||
|
Map(m reflect.Value) error |
||||||
|
MapElem(m, k, v reflect.Value) error |
||||||
|
} |
||||||
|
|
||||||
|
// SliceWalker implementations are able to handle slice elements found
|
||||||
|
// within complex structures.
|
||||||
|
type SliceWalker interface { |
||||||
|
Slice(reflect.Value) error |
||||||
|
SliceElem(int, reflect.Value) error |
||||||
|
} |
||||||
|
|
||||||
|
// ArrayWalker implementations are able to handle array elements found
|
||||||
|
// within complex structures.
|
||||||
|
type ArrayWalker interface { |
||||||
|
Array(reflect.Value) error |
||||||
|
ArrayElem(int, reflect.Value) error |
||||||
|
} |
||||||
|
|
||||||
|
// StructWalker is an interface that has methods that are called for
|
||||||
|
// structs when a Walk is done.
|
||||||
|
type StructWalker interface { |
||||||
|
Struct(reflect.Value) error |
||||||
|
StructField(reflect.StructField, reflect.Value) error |
||||||
|
} |
||||||
|
|
||||||
|
// EnterExitWalker implementations are notified before and after
|
||||||
|
// they walk deeper into complex structures (into struct fields,
|
||||||
|
// into slice elements, etc.)
|
||||||
|
type EnterExitWalker interface { |
||||||
|
Enter(Location) error |
||||||
|
Exit(Location) error |
||||||
|
} |
||||||
|
|
||||||
|
// PointerWalker implementations are notified when the value they're
|
||||||
|
// walking is a pointer or not. Pointer is called for _every_ value whether
|
||||||
|
// it is a pointer or not.
|
||||||
|
type PointerWalker interface { |
||||||
|
PointerEnter(bool) error |
||||||
|
PointerExit(bool) error |
||||||
|
} |
||||||
|
|
||||||
|
// PointerValueWalker implementations are notified with the value of
|
||||||
|
// a particular pointer when a pointer is walked. Pointer is called
|
||||||
|
// right before PointerEnter.
|
||||||
|
type PointerValueWalker interface { |
||||||
|
Pointer(reflect.Value) error |
||||||
|
} |
||||||
|
|
||||||
|
// SkipEntry can be returned from walk functions to skip walking
|
||||||
|
// the value of this field. This is only valid in the following functions:
|
||||||
|
//
|
||||||
|
// - Struct: skips all fields from being walked
|
||||||
|
// - StructField: skips walking the struct value
|
||||||
|
//
|
||||||
|
var SkipEntry = errors.New("skip this entry") |
||||||
|
|
||||||
|
// Walk takes an arbitrary value and an interface and traverses the
|
||||||
|
// value, calling callbacks on the interface if they are supported.
|
||||||
|
// The interface should implement one or more of the walker interfaces
|
||||||
|
// in this package, such as PrimitiveWalker, StructWalker, etc.
|
||||||
|
func Walk(data, walker interface{}) (err error) { |
||||||
|
v := reflect.ValueOf(data) |
||||||
|
ew, ok := walker.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
err = ew.Enter(WalkLoc) |
||||||
|
} |
||||||
|
|
||||||
|
if err == nil { |
||||||
|
err = walk(v, walker) |
||||||
|
} |
||||||
|
|
||||||
|
if ok && err == nil { |
||||||
|
err = ew.Exit(WalkLoc) |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func walk(v reflect.Value, w interface{}) (err error) { |
||||||
|
// Determine if we're receiving a pointer and if so notify the walker.
|
||||||
|
// The logic here is convoluted but very important (tests will fail if
|
||||||
|
// almost any part is changed). I will try to explain here.
|
||||||
|
//
|
||||||
|
// First, we check if the value is an interface, if so, we really need
|
||||||
|
// to check the interface's VALUE to see whether it is a pointer.
|
||||||
|
//
|
||||||
|
// Check whether the value is then a pointer. If so, then set pointer
|
||||||
|
// to true to notify the user.
|
||||||
|
//
|
||||||
|
// If we still have a pointer or an interface after the indirections, then
|
||||||
|
// we unwrap another level
|
||||||
|
//
|
||||||
|
// At this time, we also set "v" to be the dereferenced value. This is
|
||||||
|
// because once we've unwrapped the pointer we want to use that value.
|
||||||
|
pointer := false |
||||||
|
pointerV := v |
||||||
|
|
||||||
|
for { |
||||||
|
if pointerV.Kind() == reflect.Interface { |
||||||
|
if iw, ok := w.(InterfaceWalker); ok { |
||||||
|
if err = iw.Interface(pointerV); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pointerV = pointerV.Elem() |
||||||
|
} |
||||||
|
|
||||||
|
if pointerV.Kind() == reflect.Ptr { |
||||||
|
if pw, ok := w.(PointerValueWalker); ok { |
||||||
|
if err = pw.Pointer(pointerV); err != nil { |
||||||
|
if err == SkipEntry { |
||||||
|
// Skip the rest of this entry but clear the error
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pointer = true |
||||||
|
v = reflect.Indirect(pointerV) |
||||||
|
} |
||||||
|
if pw, ok := w.(PointerWalker); ok { |
||||||
|
if err = pw.PointerEnter(pointer); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
defer func(pointer bool) { |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
err = pw.PointerExit(pointer) |
||||||
|
}(pointer) |
||||||
|
} |
||||||
|
|
||||||
|
if pointer { |
||||||
|
pointerV = v |
||||||
|
} |
||||||
|
pointer = false |
||||||
|
|
||||||
|
// If we still have a pointer or interface we have to indirect another level.
|
||||||
|
switch pointerV.Kind() { |
||||||
|
case reflect.Ptr, reflect.Interface: |
||||||
|
continue |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
// We preserve the original value here because if it is an interface
|
||||||
|
// type, we want to pass that directly into the walkPrimitive, so that
|
||||||
|
// we can set it.
|
||||||
|
originalV := v |
||||||
|
if v.Kind() == reflect.Interface { |
||||||
|
v = v.Elem() |
||||||
|
} |
||||||
|
|
||||||
|
k := v.Kind() |
||||||
|
if k >= reflect.Int && k <= reflect.Complex128 { |
||||||
|
k = reflect.Int |
||||||
|
} |
||||||
|
|
||||||
|
switch k { |
||||||
|
// Primitives
|
||||||
|
case reflect.Bool, reflect.Chan, reflect.Func, reflect.Int, reflect.String, reflect.Invalid: |
||||||
|
err = walkPrimitive(originalV, w) |
||||||
|
return |
||||||
|
case reflect.Map: |
||||||
|
err = walkMap(v, w) |
||||||
|
return |
||||||
|
case reflect.Slice: |
||||||
|
err = walkSlice(v, w) |
||||||
|
return |
||||||
|
case reflect.Struct: |
||||||
|
err = walkStruct(v, w) |
||||||
|
return |
||||||
|
case reflect.Array: |
||||||
|
err = walkArray(v, w) |
||||||
|
return |
||||||
|
default: |
||||||
|
panic("unsupported type: " + k.String()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func walkMap(v reflect.Value, w interface{}) error { |
||||||
|
ew, ewok := w.(EnterExitWalker) |
||||||
|
if ewok { |
||||||
|
ew.Enter(Map) |
||||||
|
} |
||||||
|
|
||||||
|
if mw, ok := w.(MapWalker); ok { |
||||||
|
if err := mw.Map(v); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for _, k := range v.MapKeys() { |
||||||
|
kv := v.MapIndex(k) |
||||||
|
|
||||||
|
if mw, ok := w.(MapWalker); ok { |
||||||
|
if err := mw.MapElem(v, k, kv); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ew, ok := w.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
ew.Enter(MapKey) |
||||||
|
} |
||||||
|
|
||||||
|
if err := walk(k, w); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if ok { |
||||||
|
ew.Exit(MapKey) |
||||||
|
ew.Enter(MapValue) |
||||||
|
} |
||||||
|
|
||||||
|
// get the map value again as it may have changed in the MapElem call
|
||||||
|
if err := walk(v.MapIndex(k), w); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if ok { |
||||||
|
ew.Exit(MapValue) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ewok { |
||||||
|
ew.Exit(Map) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func walkPrimitive(v reflect.Value, w interface{}) error { |
||||||
|
if pw, ok := w.(PrimitiveWalker); ok { |
||||||
|
return pw.Primitive(v) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func walkSlice(v reflect.Value, w interface{}) (err error) { |
||||||
|
ew, ok := w.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
ew.Enter(Slice) |
||||||
|
} |
||||||
|
|
||||||
|
if sw, ok := w.(SliceWalker); ok { |
||||||
|
if err := sw.Slice(v); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for i := 0; i < v.Len(); i++ { |
||||||
|
elem := v.Index(i) |
||||||
|
|
||||||
|
if sw, ok := w.(SliceWalker); ok { |
||||||
|
if err := sw.SliceElem(i, elem); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ew, ok := w.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
ew.Enter(SliceElem) |
||||||
|
} |
||||||
|
|
||||||
|
if err := walk(elem, w); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if ok { |
||||||
|
ew.Exit(SliceElem) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ew, ok = w.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
ew.Exit(Slice) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func walkArray(v reflect.Value, w interface{}) (err error) { |
||||||
|
ew, ok := w.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
ew.Enter(Array) |
||||||
|
} |
||||||
|
|
||||||
|
if aw, ok := w.(ArrayWalker); ok { |
||||||
|
if err := aw.Array(v); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for i := 0; i < v.Len(); i++ { |
||||||
|
elem := v.Index(i) |
||||||
|
|
||||||
|
if aw, ok := w.(ArrayWalker); ok { |
||||||
|
if err := aw.ArrayElem(i, elem); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ew, ok := w.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
ew.Enter(ArrayElem) |
||||||
|
} |
||||||
|
|
||||||
|
if err := walk(elem, w); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if ok { |
||||||
|
ew.Exit(ArrayElem) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ew, ok = w.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
ew.Exit(Array) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func walkStruct(v reflect.Value, w interface{}) (err error) { |
||||||
|
ew, ewok := w.(EnterExitWalker) |
||||||
|
if ewok { |
||||||
|
ew.Enter(Struct) |
||||||
|
} |
||||||
|
|
||||||
|
skip := false |
||||||
|
if sw, ok := w.(StructWalker); ok { |
||||||
|
err = sw.Struct(v) |
||||||
|
if err == SkipEntry { |
||||||
|
skip = true |
||||||
|
err = nil |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if !skip { |
||||||
|
vt := v.Type() |
||||||
|
for i := 0; i < vt.NumField(); i++ { |
||||||
|
sf := vt.Field(i) |
||||||
|
f := v.FieldByIndex([]int{i}) |
||||||
|
|
||||||
|
if sw, ok := w.(StructWalker); ok { |
||||||
|
err = sw.StructField(sf, f) |
||||||
|
|
||||||
|
// SkipEntry just pretends this field doesn't even exist
|
||||||
|
if err == SkipEntry { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ew, ok := w.(EnterExitWalker) |
||||||
|
if ok { |
||||||
|
ew.Enter(StructField) |
||||||
|
} |
||||||
|
|
||||||
|
err = walk(f, w) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if ok { |
||||||
|
ew.Exit(StructField) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ewok { |
||||||
|
ew.Exit(Struct) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
Loading…
Reference in new issue