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