[FEATURE] add ability to add ports to an existing loadbalancer (#615)
This commit is contained in:
parent
bfead49c46
commit
77aa76d7f2
@ -48,6 +48,7 @@ func NewCmdNode() *cobra.Command {
|
||||
cmd.AddCommand(NewCmdNodeStop())
|
||||
cmd.AddCommand(NewCmdNodeDelete())
|
||||
cmd.AddCommand(NewCmdNodeList())
|
||||
cmd.AddCommand(NewCmdNodeEdit())
|
||||
|
||||
// add flags
|
||||
|
||||
|
113
cmd/node/nodeEdit.go
Normal file
113
cmd/node/nodeEdit.go
Normal file
@ -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
|
||||
}
|
1
go.mod
1
go.mod
@ -17,6 +17,7 @@ require (
|
||||
github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/moby/sys/mount v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf // indirect
|
||||
|
4
go.sum
4
go.sum
@ -368,6 +368,8 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@ -380,6 +382,8 @@ github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:F
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM=
|
||||
github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM=
|
||||
|
@ -33,6 +33,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
copystruct "github.com/mitchellh/copystructure"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
dockerunits "github.com/docker/go-units"
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/rancher/k3d/v4/pkg/actions"
|
||||
@ -639,3 +642,150 @@ nodeLoop:
|
||||
|
||||
return resultList
|
||||
}
|
||||
|
||||
// NodeEdit let's you update an existing node
|
||||
func NodeEdit(ctx context.Context, runtime runtimes.Runtime, existingNode, changeset *k3d.Node) error {
|
||||
|
||||
/*
|
||||
* Make a deep copy of the existing node
|
||||
*/
|
||||
|
||||
result, err := CopyNode(ctx, existingNode, CopyNodeOpts{keepState: false})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply changes
|
||||
*/
|
||||
|
||||
// === Ports ===
|
||||
if result.Ports == nil {
|
||||
result.Ports = nat.PortMap{}
|
||||
}
|
||||
for port, portbindings := range changeset.Ports {
|
||||
loopChangesetPortbindings:
|
||||
for _, portbinding := range portbindings {
|
||||
|
||||
// loop over existing portbindings to avoid port collisions (docker doesn't check for it)
|
||||
for _, existingPB := range result.Ports[port] {
|
||||
if util.IsPortBindingEqual(portbinding, existingPB) { // also matches on "equal" HostIPs (127.0.0.1, "", 0.0.0.0)
|
||||
log.Tracef("Skipping existing PortBinding: %+v", existingPB)
|
||||
continue loopChangesetPortbindings
|
||||
}
|
||||
}
|
||||
log.Tracef("Adding portbinding %+v for port %s", portbinding, port.Port())
|
||||
result.Ports[port] = append(result.Ports[port], portbinding)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Loadbalancer specifics ---
|
||||
if result.Role == k3d.LoadBalancerRole {
|
||||
nodeEditApplyLBSpecifics(ctx, result)
|
||||
}
|
||||
|
||||
// replace existing node
|
||||
return NodeReplace(ctx, runtime, existingNode, result)
|
||||
|
||||
}
|
||||
|
||||
func nodeEditApplyLBSpecifics(ctx context.Context, lbNode *k3d.Node) {
|
||||
tcp_ports := []string{}
|
||||
udp_ports := []string{}
|
||||
for index, env := range lbNode.Env {
|
||||
if strings.HasPrefix(env, "PORTS=") || strings.HasPrefix(env, "UDP_PORTS=") {
|
||||
// Remove matching environment variable from slice (does not preserve order)
|
||||
lbNode.Env[index] = lbNode.Env[len(lbNode.Env)-1] // copy last element to index of matching env
|
||||
lbNode.Env[len(lbNode.Env)-1] = "" // remove last element
|
||||
lbNode.Env = lbNode.Env[:len(lbNode.Env)-1] // truncate
|
||||
}
|
||||
}
|
||||
|
||||
for port := range lbNode.Ports {
|
||||
switch port.Proto() {
|
||||
case "tcp":
|
||||
tcp_ports = append(tcp_ports, port.Port())
|
||||
break
|
||||
case "udp":
|
||||
udp_ports = append(udp_ports, port.Port())
|
||||
break
|
||||
default:
|
||||
log.Warnf("Unknown port protocol %s for port %s", port.Proto(), port.Port())
|
||||
}
|
||||
}
|
||||
lbNode.Env = append(lbNode.Env, fmt.Sprintf("PORTS=%s", strings.Join(tcp_ports, ",")))
|
||||
lbNode.Env = append(lbNode.Env, fmt.Sprintf("UDP_PORTS=%s", strings.Join(udp_ports, ",")))
|
||||
}
|
||||
|
||||
func NodeReplace(ctx context.Context, runtime runtimes.Runtime, old, new *k3d.Node) error {
|
||||
|
||||
// rename existing node
|
||||
oldNameTemp := fmt.Sprintf("%s-%s", old.Name, util.GenerateRandomString(5))
|
||||
oldNameOriginal := old.Name
|
||||
log.Infof("Renaming existing node %s to %s...", old.Name, oldNameTemp)
|
||||
if err := runtime.RenameNode(ctx, old, oldNameTemp); err != nil {
|
||||
return err
|
||||
}
|
||||
old.Name = oldNameTemp
|
||||
|
||||
// create (not start) new node
|
||||
log.Infof("Creating new node %s...", new.Name)
|
||||
if err := NodeCreate(ctx, runtime, new, k3d.NodeCreateOpts{Wait: true}); err != nil {
|
||||
if err := runtime.RenameNode(ctx, old, oldNameOriginal); err != nil {
|
||||
return fmt.Errorf("Failed to create new node. Also failed to rename %s back to %s: %+v", old.Name, oldNameOriginal, err)
|
||||
}
|
||||
return fmt.Errorf("Failed to create new node. Brought back old node: %+v", err)
|
||||
}
|
||||
|
||||
// stop existing/old node
|
||||
log.Infof("Stopping existing node %s...", old.Name)
|
||||
if err := runtime.StopNode(ctx, old); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start new node
|
||||
log.Infof("Starting new node %s...", new.Name)
|
||||
if err := NodeStart(ctx, runtime, new, k3d.NodeStartOpts{Wait: true}); err != nil {
|
||||
if err := NodeDelete(ctx, runtime, new, k3d.NodeDeleteOpts{SkipLBUpdate: true}); err != nil {
|
||||
return fmt.Errorf("Failed to start new node. Also failed to rollback: %+v", err)
|
||||
}
|
||||
if err := runtime.RenameNode(ctx, old, oldNameOriginal); err != nil {
|
||||
return fmt.Errorf("Failed to start new node. Also failed to rename %s back to %s: %+v", old.Name, oldNameOriginal, err)
|
||||
}
|
||||
old.Name = oldNameOriginal
|
||||
if err := NodeStart(ctx, runtime, old, k3d.NodeStartOpts{Wait: true}); err != nil {
|
||||
return fmt.Errorf("Failed to start new node. Also failed to restart old node: %+v", err)
|
||||
}
|
||||
return fmt.Errorf("Failed to start new node. Rolled back: %+v", err)
|
||||
}
|
||||
|
||||
// cleanup: delete old node
|
||||
log.Infof("Deleting old node %s...", old.Name)
|
||||
if err := NodeDelete(ctx, runtime, old, k3d.NodeDeleteOpts{SkipLBUpdate: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// done
|
||||
return nil
|
||||
}
|
||||
|
||||
type CopyNodeOpts struct {
|
||||
keepState bool
|
||||
}
|
||||
|
||||
func CopyNode(ctx context.Context, src *k3d.Node, opts CopyNodeOpts) (*k3d.Node, error) {
|
||||
|
||||
targetCopy, err := copystruct.Copy(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := targetCopy.(*k3d.Node)
|
||||
|
||||
if !opts.keepState {
|
||||
// ensure that node state is empty
|
||||
result.State = k3d.NodeState{}
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
@ -447,3 +447,21 @@ func (d Docker) GetNodesInNetwork(ctx context.Context, network string) ([]*k3d.N
|
||||
|
||||
return connectedNodes, nil
|
||||
}
|
||||
|
||||
func (d Docker) RenameNode(ctx context.Context, node *k3d.Node, newName string) error {
|
||||
// get the container for the given node
|
||||
container, err := getNodeContainer(ctx, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create docker client
|
||||
docker, err := GetDockerClient()
|
||||
if err != nil {
|
||||
log.Errorln("Failed to create docker client")
|
||||
return err
|
||||
}
|
||||
defer docker.Close()
|
||||
|
||||
return docker.ContainerRename(ctx, container.ID, newName)
|
||||
}
|
||||
|
@ -243,14 +243,6 @@ func TranslateContainerDetailsToNode(containerDetails types.ContainerJSON) (*k3d
|
||||
}
|
||||
}
|
||||
|
||||
// env vars: only copy K3S_* and K3D_* // FIXME: should we really do this? Might be unexpected, if user has e.g. HTTP_PROXY vars
|
||||
env := []string{}
|
||||
for _, envVar := range containerDetails.Config.Env {
|
||||
if strings.HasPrefix(envVar, "K3D_") || strings.HasPrefix(envVar, "K3S_") {
|
||||
env = append(env, envVar)
|
||||
}
|
||||
}
|
||||
|
||||
// labels: only copy k3d.* labels
|
||||
labels := map[string]string{}
|
||||
for k, v := range containerDetails.Config.Labels {
|
||||
@ -277,7 +269,7 @@ func TranslateContainerDetailsToNode(containerDetails types.ContainerJSON) (*k3d
|
||||
Role: k3d.NodeRoles[containerDetails.Config.Labels[k3d.LabelRole]],
|
||||
Image: containerDetails.Image,
|
||||
Volumes: containerDetails.HostConfig.Binds,
|
||||
Env: env,
|
||||
Env: containerDetails.Config.Env,
|
||||
Cmd: containerDetails.Config.Cmd,
|
||||
Args: []string{}, // empty, since Cmd already contains flags
|
||||
Ports: containerDetails.HostConfig.PortBindings,
|
||||
|
@ -52,6 +52,7 @@ type Runtime interface {
|
||||
GetHost() string
|
||||
CreateNode(context.Context, *k3d.Node) error
|
||||
DeleteNode(context.Context, *k3d.Node) error
|
||||
RenameNode(context.Context, *k3d.Node, string) error
|
||||
GetNodesByLabel(context.Context, map[string]string) ([]*k3d.Node, error)
|
||||
GetNode(context.Context, *k3d.Node) (*k3d.Node, error)
|
||||
GetNodeStatus(context.Context, *k3d.Node) (bool, string, error)
|
||||
|
@ -25,6 +25,7 @@ package util
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -45,3 +46,21 @@ func GetFreePort() (int, error) {
|
||||
|
||||
return tcpListener.Addr().(*net.TCPAddr).Port, nil
|
||||
}
|
||||
|
||||
var equalHostIPs = map[string]interface{}{
|
||||
"": nil,
|
||||
"127.0.0.1": nil,
|
||||
"0.0.0.0": nil,
|
||||
"localhost": nil,
|
||||
}
|
||||
|
||||
func IsPortBindingEqual(a, b nat.PortBinding) bool {
|
||||
if a.HostPort == b.HostPort {
|
||||
if _, ok := equalHostIPs[a.HostIP]; ok {
|
||||
if _, ok := equalHostIPs[b.HostIP]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
39
tests/test_node_edit.sh
Executable file
39
tests/test_node_edit.sh
Executable file
@ -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
|
21
vendor/github.com/mitchellh/copystructure/LICENSE
generated
vendored
Normal file
21
vendor/github.com/mitchellh/copystructure/LICENSE
generated
vendored
Normal file
@ -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.
|
21
vendor/github.com/mitchellh/copystructure/README.md
generated
vendored
Normal file
21
vendor/github.com/mitchellh/copystructure/README.md
generated
vendored
Normal file
@ -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.
|
15
vendor/github.com/mitchellh/copystructure/copier_time.go
generated
vendored
Normal file
15
vendor/github.com/mitchellh/copystructure/copier_time.go
generated
vendored
Normal file
@ -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
|
||||
}
|
631
vendor/github.com/mitchellh/copystructure/copystructure.go
generated
vendored
Normal file
631
vendor/github.com/mitchellh/copystructure/copystructure.go
generated
vendored
Normal file
@ -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
|
||||
}
|
5
vendor/github.com/mitchellh/copystructure/go.mod
generated
vendored
Normal file
5
vendor/github.com/mitchellh/copystructure/go.mod
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/mitchellh/copystructure
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/mitchellh/reflectwalk v1.0.2
|
2
vendor/github.com/mitchellh/copystructure/go.sum
generated
vendored
Normal file
2
vendor/github.com/mitchellh/copystructure/go.sum
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
1
vendor/github.com/mitchellh/reflectwalk/.travis.yml
generated
vendored
Normal file
1
vendor/github.com/mitchellh/reflectwalk/.travis.yml
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
language: go
|
21
vendor/github.com/mitchellh/reflectwalk/LICENSE
generated
vendored
Normal file
21
vendor/github.com/mitchellh/reflectwalk/LICENSE
generated
vendored
Normal file
@ -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.
|
6
vendor/github.com/mitchellh/reflectwalk/README.md
generated
vendored
Normal file
6
vendor/github.com/mitchellh/reflectwalk/README.md
generated
vendored
Normal file
@ -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.
|
1
vendor/github.com/mitchellh/reflectwalk/go.mod
generated
vendored
Normal file
1
vendor/github.com/mitchellh/reflectwalk/go.mod
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
module github.com/mitchellh/reflectwalk
|
19
vendor/github.com/mitchellh/reflectwalk/location.go
generated
vendored
Normal file
19
vendor/github.com/mitchellh/reflectwalk/location.go
generated
vendored
Normal file
@ -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
|
||||
)
|
16
vendor/github.com/mitchellh/reflectwalk/location_string.go
generated
vendored
Normal file
16
vendor/github.com/mitchellh/reflectwalk/location_string.go
generated
vendored
Normal file
@ -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]]
|
||||
}
|
420
vendor/github.com/mitchellh/reflectwalk/reflectwalk.go
generated
vendored
Normal file
420
vendor/github.com/mitchellh/reflectwalk/reflectwalk.go
generated
vendored
Normal file
@ -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
|
||||
}
|
5
vendor/modules.txt
vendored
5
vendor/modules.txt
vendored
@ -145,11 +145,16 @@ github.com/json-iterator/go
|
||||
github.com/liggitt/tabwriter
|
||||
# github.com/magiconair/properties v1.8.5
|
||||
github.com/magiconair/properties
|
||||
# github.com/mitchellh/copystructure v1.2.0
|
||||
## explicit
|
||||
github.com/mitchellh/copystructure
|
||||
# github.com/mitchellh/go-homedir v1.1.0
|
||||
## explicit
|
||||
github.com/mitchellh/go-homedir
|
||||
# github.com/mitchellh/mapstructure v1.4.1
|
||||
github.com/mitchellh/mapstructure
|
||||
# github.com/mitchellh/reflectwalk v1.0.2
|
||||
github.com/mitchellh/reflectwalk
|
||||
# github.com/moby/sys/mount v0.2.0
|
||||
## explicit
|
||||
github.com/moby/sys/mount
|
||||
|
Loading…
Reference in New Issue
Block a user