diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 000000000..dc2c6a1ff --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,12 @@ +with-expecter: true +filename: "mock_{{.InterfaceNameSnake}}.go" +dir: "{{.InterfaceDir}}/mocks" +mockname: "{{.Mock}}{{.InterfaceName}}" +outpkg: "{{.PackageName}}" +packages: + github.com/openshift/lvm-operator/pkg/lsblk: + interfaces: + LSBLK: + github.com/openshift/lvm-operator/pkg/lvm: + interfaces: + LVM: \ No newline at end of file diff --git a/Makefile b/Makefile index 1722c7d08..faf6df4c7 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,10 @@ SKIP_RANGE ?= manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases -generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. +mocks: mockery ## Generate mocks for unit test code + $(shell $(MOCKERY) --log-level error) + +generate: controller-gen mocks ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. Also retriggers mock generation $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." fmt: ## Run go fmt against code. @@ -319,6 +322,10 @@ GINKGO = $(shell pwd)/bin/ginkgo ginkgo: ## Download ginkgo and gomega locally if necessary. $(call go-get-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo@v2.9.5) +MOCKERY = $(shell pwd)/bin/mockery +mockery: ## Download mockery and add locally if necessary + $(call go-get-tool,$(MOCKERY),github.com/vektra/mockery/v2@v2.33.2) + # go-get-tool will 'go get' any package $2 and install it to $1. PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) define go-get-tool diff --git a/cmd/vgmanager/main.go b/cmd/vgmanager/main.go index 6a79a9183..f0027808d 100644 --- a/cmd/vgmanager/main.go +++ b/cmd/vgmanager/main.go @@ -23,6 +23,7 @@ import ( lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" "github.com/openshift/lvm-operator/pkg/filter" "github.com/openshift/lvm-operator/pkg/lsblk" + "github.com/openshift/lvm-operator/pkg/lvm" "github.com/openshift/lvm-operator/pkg/lvmd" "github.com/openshift/lvm-operator/pkg/vgmanager" @@ -85,6 +86,7 @@ func main() { LVMD: lvmd.DefaultConfigurator(), Scheme: mgr.GetScheme(), LSBLK: lsblk.NewDefaultHostLSBLK(), + LVM: lvm.NewDefaultHostLVM(), NodeName: os.Getenv("NODE_NAME"), Namespace: os.Getenv("POD_NAMESPACE"), Filters: filter.DefaultFilters, diff --git a/controllers/constants.go b/controllers/constants.go index 18b0b19dd..583aca421 100644 --- a/controllers/constants.go +++ b/controllers/constants.go @@ -81,7 +81,6 @@ const ( CSIKubeletRootDir = "/var/lib/kubelet/" NodeContainerName = "topolvm-node" TopolvmNodeContainerHealthzName = "healthz" - LvmdConfigFile = "/etc/topolvm/lvmd.yaml" DefaultCSISocket = "/run/topolvm/csi-topolvm.sock" DefaultLVMdSocket = "/run/lvmd/lvmd.sock" diff --git a/controllers/topolvm_node.go b/controllers/topolvm_node.go index 639427980..a2109809c 100644 --- a/controllers/topolvm_node.go +++ b/controllers/topolvm_node.go @@ -23,6 +23,7 @@ import ( "strings" lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" + "github.com/openshift/lvm-operator/pkg/lvmd" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -137,7 +138,7 @@ func getNodeDaemonSet(lvmCluster *lvmv1alpha1.LVMCluster, namespace string, init {Name: "lvmd-config-dir", VolumeSource: corev1.VolumeSource{ HostPath: &corev1.HostPathVolumeSource{ - Path: filepath.Dir(LvmdConfigFile), + Path: filepath.Dir(lvmd.DefaultFileConfigPath), Type: &hostPathDirectory}}}, {Name: "lvmd-socket-dir", VolumeSource: corev1.VolumeSource{ @@ -202,11 +203,11 @@ func getNodeInitContainer(initImage string) *corev1.Container { command := []string{ "/usr/bin/bash", "-c", - fmt.Sprintf("until [ -f %s ]; do echo waiting for lvmd config file; sleep 5; done", LvmdConfigFile), + fmt.Sprintf("until [ -f %s ]; do echo waiting for lvmd config file; sleep 5; done", lvmd.DefaultFileConfigPath), } volumeMounts := []corev1.VolumeMount{ - {Name: "lvmd-config-dir", MountPath: filepath.Dir(LvmdConfigFile)}, + {Name: "lvmd-config-dir", MountPath: filepath.Dir(lvmd.DefaultFileConfigPath)}, } fileChecker := &corev1.Container{ @@ -228,7 +229,7 @@ func getNodeInitContainer(initImage string) *corev1.Container { func getLvmdContainer() *corev1.Container { command := []string{ "/lvmd", - fmt.Sprintf("--config=%s", LvmdConfigFile), + fmt.Sprintf("--config=%s", lvmd.DefaultFileConfigPath), "--container=true", } @@ -241,7 +242,7 @@ func getLvmdContainer() *corev1.Container { volumeMounts := []corev1.VolumeMount{ {Name: "lvmd-socket-dir", MountPath: filepath.Dir(DefaultLVMdSocket)}, - {Name: "lvmd-config-dir", MountPath: filepath.Dir(LvmdConfigFile)}, + {Name: "lvmd-config-dir", MountPath: filepath.Dir(lvmd.DefaultFileConfigPath)}, } privilege := true diff --git a/go.mod b/go.mod index 64c177953..e19d06c0f 100644 --- a/go.mod +++ b/go.mod @@ -80,6 +80,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.10.1 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect diff --git a/go.sum b/go.sum index e6033cfec..8739b35f6 100644 --- a/go.sum +++ b/go.sum @@ -367,6 +367,7 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go index a9eb4df25..ccdd35316 100644 --- a/pkg/filter/filter.go +++ b/pkg/filter/filter.go @@ -20,7 +20,6 @@ import ( "fmt" "strings" - "github.com/openshift/lvm-operator/pkg/internal/exec" "github.com/openshift/lvm-operator/pkg/lsblk" "github.com/openshift/lvm-operator/pkg/lvm" ) @@ -51,31 +50,31 @@ const ( usableDeviceType = "usableDeviceType" ) -type Filters map[string]func(lsblk.BlockDevice, exec.Executor) (bool, error) +type Filters map[string]func(lsblk.BlockDevice) (bool, error) -func DefaultFilters(lsblkInstance lsblk.LSBLK) Filters { +func DefaultFilters(lvm lvm.LVM, lsblkInstance lsblk.LSBLK) Filters { return Filters{ - notReadOnly: func(dev lsblk.BlockDevice, _ exec.Executor) (bool, error) { + notReadOnly: func(dev lsblk.BlockDevice) (bool, error) { return !dev.ReadOnly, nil }, - notSuspended: func(dev lsblk.BlockDevice, _ exec.Executor) (bool, error) { + notSuspended: func(dev lsblk.BlockDevice) (bool, error) { matched := dev.State != StateSuspended return matched, nil }, - noBiosBootInPartLabel: func(dev lsblk.BlockDevice, _ exec.Executor) (bool, error) { + noBiosBootInPartLabel: func(dev lsblk.BlockDevice) (bool, error) { biosBootInPartLabel := strings.Contains(strings.ToLower(dev.PartLabel), strings.ToLower("bios")) || strings.Contains(strings.ToLower(dev.PartLabel), strings.ToLower("boot")) return !biosBootInPartLabel, nil }, - noReservedInPartLabel: func(dev lsblk.BlockDevice, _ exec.Executor) (bool, error) { + noReservedInPartLabel: func(dev lsblk.BlockDevice) (bool, error) { reservedInPartLabel := strings.Contains(strings.ToLower(dev.PartLabel), "reserved") return !reservedInPartLabel, nil }, - noValidFilesystemSignature: func(dev lsblk.BlockDevice, e exec.Executor) (bool, error) { + noValidFilesystemSignature: func(dev lsblk.BlockDevice) (bool, error) { // if no fs type is set, it's always okay if dev.FSType == "" { return true, nil @@ -84,7 +83,7 @@ func DefaultFilters(lsblkInstance lsblk.LSBLK) Filters { // if fstype is set to LVM2_member then it already was created as a PV // this means that if the disk has no children, we can safely reuse it if it's a valid LVM PV. if dev.FSType == "LVM2_member" && !dev.HasChildren() { - pvs, err := lvm.ListPhysicalVolumes(e, "") + pvs, err := lvm.ListPVs("") if err != nil { return false, fmt.Errorf("could not determine if block device has valid filesystem signature, since it is flagged as LVM2_member but physical volumes could not be verified: %w", err) } @@ -108,17 +107,17 @@ func DefaultFilters(lsblkInstance lsblk.LSBLK) Filters { return false, nil }, - noBindMounts: func(dev lsblk.BlockDevice, _ exec.Executor) (bool, error) { - hasBindMounts, _, err := lsblkInstance.HasBindMounts(dev) - return !hasBindMounts, err - }, - - noChildren: func(dev lsblk.BlockDevice, _ exec.Executor) (bool, error) { + noChildren: func(dev lsblk.BlockDevice) (bool, error) { hasChildren := dev.HasChildren() return !hasChildren, nil }, - usableDeviceType: func(dev lsblk.BlockDevice, executor exec.Executor) (bool, error) { + noBindMounts: func(dev lsblk.BlockDevice) (bool, error) { + hasBindMounts, _, err := lsblkInstance.HasBindMounts(dev) + return !hasBindMounts, err + }, + + usableDeviceType: func(dev lsblk.BlockDevice) (bool, error) { switch dev.Type { case DeviceTypeLoop: // check loop device isn't being used by kubernetes diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go index 899e98ccd..14bafe5ad 100644 --- a/pkg/filter/filter_test.go +++ b/pkg/filter/filter_test.go @@ -20,7 +20,7 @@ func TestNotReadOnly(t *testing.T) { {label: "tc true", device: lsblk.BlockDevice{ReadOnly: true}, expected: false, expectErr: false}, } for _, tc := range testcases { - result, err := DefaultFilters(nil)[notReadOnly](tc.device, nil) + result, err := DefaultFilters(nil, nil)[notReadOnly](tc.device) assert.Equal(t, tc.expected, result) if tc.expectErr { assert.Error(t, err) @@ -37,7 +37,7 @@ func TestNotSuspended(t *testing.T) { {label: "tc running", device: lsblk.BlockDevice{State: "running"}, expected: true, expectErr: false}, } for _, tc := range testcases { - result, err := DefaultFilters(nil)[notSuspended](tc.device, nil) + result, err := DefaultFilters(nil, nil)[notSuspended](tc.device) assert.Equal(t, tc.expected, result) if tc.expectErr { assert.Error(t, err) @@ -54,7 +54,7 @@ func TestNoFilesystemSignature(t *testing.T) { {label: "tc swap", device: lsblk.BlockDevice{FSType: "swap"}, expected: false, expectErr: false}, } for _, tc := range testcases { - result, err := DefaultFilters(nil)[noValidFilesystemSignature](tc.device, nil) + result, err := DefaultFilters(nil, nil)[noValidFilesystemSignature](tc.device) assert.Equal(t, tc.expected, result) if tc.expectErr { assert.Error(t, err) @@ -70,7 +70,7 @@ func TestNoChildren(t *testing.T) { {label: "tc no child", device: lsblk.BlockDevice{Name: "dev2", Children: []lsblk.BlockDevice{}}, expected: true, expectErr: false}, } for _, tc := range testcases { - result, err := DefaultFilters(nil)[noChildren](tc.device, nil) + result, err := DefaultFilters(nil, nil)[noChildren](tc.device) assert.Equal(t, tc.expected, result) if tc.expectErr { assert.Error(t, err) @@ -86,7 +86,7 @@ func TestIsUsableDeviceType(t *testing.T) { {label: "tc Disk", device: lsblk.BlockDevice{Name: "dev2", Type: "disk"}, expected: true, expectErr: false}, } for _, tc := range testcases { - result, err := DefaultFilters(nil)[usableDeviceType](tc.device, nil) + result, err := DefaultFilters(nil, nil)[usableDeviceType](tc.device) assert.Equal(t, tc.expected, result) if tc.expectErr { assert.Error(t, err) @@ -106,7 +106,7 @@ func TestNoBiosBootInPartLabel(t *testing.T) { {label: "tc 6", device: lsblk.BlockDevice{Name: "dev6", PartLabel: "BOOT"}, expected: false, expectErr: false}, } for _, tc := range testcases { - result, err := DefaultFilters(nil)[noBiosBootInPartLabel](tc.device, nil) + result, err := DefaultFilters(nil, nil)[noBiosBootInPartLabel](tc.device) assert.Equal(t, tc.expected, result) if tc.expectErr { assert.Error(t, err) @@ -125,7 +125,7 @@ func TestNoReservedInPartLabel(t *testing.T) { {label: "tc 5", device: lsblk.BlockDevice{Name: "dev5", PartLabel: "Reserved"}, expected: false}, } for _, tc := range testcases { - result, err := DefaultFilters(nil)[noReservedInPartLabel](tc.device, nil) + result, err := DefaultFilters(nil, nil)[noReservedInPartLabel](tc.device) assert.NoError(t, err) assert.Equal(t, tc.expected, result) } diff --git a/pkg/internal/exec/exec.go b/pkg/internal/exec/exec.go index c1ddcad48..6b42826e6 100644 --- a/pkg/internal/exec/exec.go +++ b/pkg/internal/exec/exec.go @@ -24,7 +24,6 @@ import ( var ( nsenterPath = "/usr/bin/nsenter" - losetupPath = "/usr/sbin/losetup" ) // Executor is the interface for running exec commands @@ -42,7 +41,7 @@ func (*CommandExecutor) ExecuteCommandWithOutput(command string, arg ...string) return runCommandWithOutput(cmd) } -// ExecuteCommandWithOutput executes a command with output using nsenter +// ExecuteCommandWithOutputAsHost executes a command with output using nsenter func (*CommandExecutor) ExecuteCommandWithOutputAsHost(command string, arg ...string) (string, error) { args := append([]string{"-m", "-u", "-i", "-n", "-p", "-t", "1", command}, arg...) cmd := exec.Command(nsenterPath, args...) diff --git a/pkg/lsblk/mocks/mock_lsblk.go b/pkg/lsblk/mocks/mock_lsblk.go new file mode 100644 index 000000000..75ac2c7b7 --- /dev/null +++ b/pkg/lsblk/mocks/mock_lsblk.go @@ -0,0 +1,199 @@ +// Code generated by mockery v2.33.2. DO NOT EDIT. + +package lsblk + +import ( + lsblk "github.com/openshift/lvm-operator/pkg/lsblk" + mock "github.com/stretchr/testify/mock" +) + +// MockLSBLK is an autogenerated mock type for the LSBLK type +type MockLSBLK struct { + mock.Mock +} + +type MockLSBLK_Expecter struct { + mock *mock.Mock +} + +func (_m *MockLSBLK) EXPECT() *MockLSBLK_Expecter { + return &MockLSBLK_Expecter{mock: &_m.Mock} +} + +// HasBindMounts provides a mock function with given fields: b +func (_m *MockLSBLK) HasBindMounts(b lsblk.BlockDevice) (bool, string, error) { + ret := _m.Called(b) + + var r0 bool + var r1 string + var r2 error + if rf, ok := ret.Get(0).(func(lsblk.BlockDevice) (bool, string, error)); ok { + return rf(b) + } + if rf, ok := ret.Get(0).(func(lsblk.BlockDevice) bool); ok { + r0 = rf(b) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(lsblk.BlockDevice) string); ok { + r1 = rf(b) + } else { + r1 = ret.Get(1).(string) + } + + if rf, ok := ret.Get(2).(func(lsblk.BlockDevice) error); ok { + r2 = rf(b) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockLSBLK_HasBindMounts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasBindMounts' +type MockLSBLK_HasBindMounts_Call struct { + *mock.Call +} + +// HasBindMounts is a helper method to define mock.On call +// - b lsblk.BlockDevice +func (_e *MockLSBLK_Expecter) HasBindMounts(b interface{}) *MockLSBLK_HasBindMounts_Call { + return &MockLSBLK_HasBindMounts_Call{Call: _e.mock.On("HasBindMounts", b)} +} + +func (_c *MockLSBLK_HasBindMounts_Call) Run(run func(b lsblk.BlockDevice)) *MockLSBLK_HasBindMounts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(lsblk.BlockDevice)) + }) + return _c +} + +func (_c *MockLSBLK_HasBindMounts_Call) Return(_a0 bool, _a1 string, _a2 error) *MockLSBLK_HasBindMounts_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockLSBLK_HasBindMounts_Call) RunAndReturn(run func(lsblk.BlockDevice) (bool, string, error)) *MockLSBLK_HasBindMounts_Call { + _c.Call.Return(run) + return _c +} + +// IsUsableLoopDev provides a mock function with given fields: b +func (_m *MockLSBLK) IsUsableLoopDev(b lsblk.BlockDevice) (bool, error) { + ret := _m.Called(b) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(lsblk.BlockDevice) (bool, error)); ok { + return rf(b) + } + if rf, ok := ret.Get(0).(func(lsblk.BlockDevice) bool); ok { + r0 = rf(b) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(lsblk.BlockDevice) error); ok { + r1 = rf(b) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLSBLK_IsUsableLoopDev_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsUsableLoopDev' +type MockLSBLK_IsUsableLoopDev_Call struct { + *mock.Call +} + +// IsUsableLoopDev is a helper method to define mock.On call +// - b lsblk.BlockDevice +func (_e *MockLSBLK_Expecter) IsUsableLoopDev(b interface{}) *MockLSBLK_IsUsableLoopDev_Call { + return &MockLSBLK_IsUsableLoopDev_Call{Call: _e.mock.On("IsUsableLoopDev", b)} +} + +func (_c *MockLSBLK_IsUsableLoopDev_Call) Run(run func(b lsblk.BlockDevice)) *MockLSBLK_IsUsableLoopDev_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(lsblk.BlockDevice)) + }) + return _c +} + +func (_c *MockLSBLK_IsUsableLoopDev_Call) Return(_a0 bool, _a1 error) *MockLSBLK_IsUsableLoopDev_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLSBLK_IsUsableLoopDev_Call) RunAndReturn(run func(lsblk.BlockDevice) (bool, error)) *MockLSBLK_IsUsableLoopDev_Call { + _c.Call.Return(run) + return _c +} + +// ListBlockDevices provides a mock function with given fields: +func (_m *MockLSBLK) ListBlockDevices() ([]lsblk.BlockDevice, error) { + ret := _m.Called() + + var r0 []lsblk.BlockDevice + var r1 error + if rf, ok := ret.Get(0).(func() ([]lsblk.BlockDevice, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []lsblk.BlockDevice); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]lsblk.BlockDevice) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLSBLK_ListBlockDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBlockDevices' +type MockLSBLK_ListBlockDevices_Call struct { + *mock.Call +} + +// ListBlockDevices is a helper method to define mock.On call +func (_e *MockLSBLK_Expecter) ListBlockDevices() *MockLSBLK_ListBlockDevices_Call { + return &MockLSBLK_ListBlockDevices_Call{Call: _e.mock.On("ListBlockDevices")} +} + +func (_c *MockLSBLK_ListBlockDevices_Call) Run(run func()) *MockLSBLK_ListBlockDevices_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockLSBLK_ListBlockDevices_Call) Return(_a0 []lsblk.BlockDevice, _a1 error) *MockLSBLK_ListBlockDevices_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLSBLK_ListBlockDevices_Call) RunAndReturn(run func() ([]lsblk.BlockDevice, error)) *MockLSBLK_ListBlockDevices_Call { + _c.Call.Return(run) + return _c +} + +// NewMockLSBLK creates a new instance of MockLSBLK. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockLSBLK(t interface { + mock.TestingT + Cleanup(func()) +}) *MockLSBLK { + mock := &MockLSBLK{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/lvm/lvm.go b/pkg/lvm/lvm.go index a198e7c91..29c6d830c 100644 --- a/pkg/lvm/lvm.go +++ b/pkg/lvm/lvm.go @@ -23,7 +23,7 @@ import ( osexec "os/exec" "strings" - "github.com/openshift/lvm-operator/pkg/internal/exec" + lvmexec "github.com/openshift/lvm-operator/pkg/internal/exec" ) type lvmError string @@ -35,21 +35,23 @@ var ( ) const ( - defaultChunkSize = "128" - lvmCmd = "/usr/sbin/lvm" - vgCreateCmd = "/usr/sbin/vgcreate" - vgExtendCmd = "/usr/sbin/vgextend" - vgRemoveCmd = "/usr/sbin/vgremove" - pvRemoveCmd = "/usr/sbin/pvremove" - lvCreateCmd = "/usr/sbin/lvcreate" - lvExtendCmd = "/usr/sbin/lvextend" - lvRemoveCmd = "/usr/sbin/lvremove" - lvChangeCmd = "/usr/sbin/lvchange" - lvmDevicesCmd = "/usr/sbin/lvmdevices" + DefaultChunkSize = "128" + + lvmCmd = "/usr/sbin/lvm" + vgCreateCmd = "/usr/sbin/vgcreate" + vgChangeCmd = "/usr/sbin/vgchange" + vgExtendCmd = "/usr/sbin/vgextend" + vgRemoveCmd = "/usr/sbin/vgremove" + pvRemoveCmd = "/usr/sbin/pvremove" + lvCreateCmd = "/usr/sbin/lvcreate" + lvExtendCmd = "/usr/sbin/lvextend" + lvRemoveCmd = "/usr/sbin/lvremove" + lvChangeCmd = "/usr/sbin/lvchange" + lvmDevicesCmd = "/usr/sbin/lvmdevices" ) -// vgsOutput represents the output of the `vgs --reportformat json` command -type vgsOutput struct { +// VGReport represents the output of the `vgs --reportformat json` command +type VGReport struct { Report []struct { Vg []struct { Name string `json:"vg_name"` @@ -58,25 +60,58 @@ type vgsOutput struct { } `json:"report"` } -// pvsOutput represents the output of the `pvs --reportformat json` command -type pvsOutput struct { +// PVReport represents the output of the `pvs --reportformat json` command +type PVReport struct { Report []struct { Pv []PhysicalVolume `json:"pv"` } `json:"report"` } -// lvsOutput represents the output of the `lvs --reportformat json` command -type lvsOutput struct { - Report []struct { - Lv []struct { - Name string `json:"lv_name"` - VgName string `json:"vg_name"` - PoolName string `json:"pool_lv"` - LvAttr string `json:"lv_attr"` - LvSize string `json:"lv_size"` - MetadataPercent string `json:"metadata_percent"` - } `json:"lv"` - } `json:"report"` +// LVReport represents the output of the `lvs --reportformat json` command +type LVReport struct { + Report []LVReportItem `json:"report"` +} + +type LVReportItem struct { + Lv []LogicalVolume `json:"lv"` +} + +type LogicalVolume struct { + Name string `json:"lv_name"` + VgName string `json:"vg_name"` + PoolName string `json:"pool_lv"` + LvAttr string `json:"lv_attr"` + LvSize string `json:"lv_size"` + MetadataPercent string `json:"metadata_percent"` +} + +type LVM interface { + CreateVG(vg VolumeGroup) error + ExtendVG(vg VolumeGroup, pvs []string) (VolumeGroup, error) + DeleteVG(vg VolumeGroup) error + GetVG(name string) (VolumeGroup, error) + + ListPVs(vgName string) ([]PhysicalVolume, error) + ListVGs() ([]VolumeGroup, error) + ListLVsByName(vgName string) ([]string, error) + ListLVs(vgName string) (*LVReport, error) + + LVExists(lvName, vgName string) (bool, error) + CreateLV(lvName, vgName string, sizePercent int) error + ExtendLV(lvName, vgName string, sizePercent int) error + DeleteLV(lvName, vgName string) error +} + +type HostLVM struct { + lvmexec.Executor +} + +func NewDefaultHostLVM() *HostLVM { + return NewHostLVM(&lvmexec.CommandExecutor{}) +} + +func NewHostLVM(executor lvmexec.Executor) *HostLVM { + return &HostLVM{executor} } // VolumeGroup represents a volume group of linux lvm. @@ -119,49 +154,7 @@ type PhysicalVolume struct { } // Create creates a new volume group -func (vg VolumeGroup) Create(exec exec.Executor, pvs []string) error { - if vg.Name == "" { - return fmt.Errorf("failed to create volume group. Volume group name is empty") - } - - if len(pvs) == 0 { - return fmt.Errorf("failed to create volume group. Physical volume list is empty") - } - - args := []string{vg.Name} - args = append(args, pvs...) - - _, err := exec.ExecuteCommandWithOutputAsHost(vgCreateCmd, args...) - if err != nil { - return fmt.Errorf("failed to create volume group %q. %v", vg.Name, err) - } - - return nil -} - -// Extend extends the volume group only if new physical volumes are available -func (vg VolumeGroup) Extend(exec exec.Executor, pvs []string) error { - if vg.Name == "" { - return fmt.Errorf("failed to extend volume group. Volume group name is empty") - } - - if len(pvs) == 0 { - return fmt.Errorf("failed to extend volume group. Physical volume list is empty") - } - - args := []string{vg.Name} - args = append(args, pvs...) - - _, err := exec.ExecuteCommandWithOutputAsHost(vgExtendCmd, args...) - if err != nil { - return fmt.Errorf("failed to extend volume group %q. %v", vg.Name, err) - } - - return nil -} - -// CreateVG creates a new volume group -func (vg VolumeGroup) CreateVG(exec exec.Executor) error { +func (hlvm *HostLVM) CreateVG(vg VolumeGroup) error { if vg.Name == "" { return fmt.Errorf("failed to create volume group. Volume group name is empty") } @@ -176,7 +169,7 @@ func (vg VolumeGroup) CreateVG(exec exec.Executor) error { args = append(args, pv.PvName) } - _, err := exec.ExecuteCommandWithOutputAsHost(vgCreateCmd, args...) + _, err := hlvm.ExecuteCommandWithOutputAsHost(vgCreateCmd, args...) if err != nil { return fmt.Errorf("failed to create volume group %q. %v", vg.Name, err) } @@ -184,8 +177,8 @@ func (vg VolumeGroup) CreateVG(exec exec.Executor) error { return nil } -// ExtendVG extends the volume group only if new physical volumes are available -func (vg VolumeGroup) ExtendVG(exec exec.Executor, pvs []string) (VolumeGroup, error) { +// ExtendVG Extend extends the volume group only if new physical volumes are available +func (hlvm *HostLVM) ExtendVG(vg VolumeGroup, pvs []string) (VolumeGroup, error) { if vg.Name == "" { return VolumeGroup{}, fmt.Errorf("failed to extend volume group. Volume group name is empty") } @@ -197,7 +190,7 @@ func (vg VolumeGroup) ExtendVG(exec exec.Executor, pvs []string) (VolumeGroup, e args := []string{vg.Name} args = append(args, pvs...) - _, err := exec.ExecuteCommandWithOutputAsHost(vgExtendCmd, args...) + _, err := hlvm.ExecuteCommandWithOutputAsHost(vgExtendCmd, args...) if err != nil { return VolumeGroup{}, fmt.Errorf("failed to extend volume group %q. %v", vg.Name, err) } @@ -210,26 +203,33 @@ func (vg VolumeGroup) ExtendVG(exec exec.Executor, pvs []string) (VolumeGroup, e } // Delete deletes a volume group and the physical volumes associated with it -func (vg VolumeGroup) Delete(e exec.Executor) error { +func (hlvm *HostLVM) DeleteVG(vg VolumeGroup) error { + // Deactivate Volume Group + vgArgs := []string{"-an", vg.Name} + _, err := hlvm.ExecuteCommandWithOutputAsHost(vgChangeCmd, vgArgs...) + if err != nil { + return fmt.Errorf("failed to remove volume group %q: %w", vg.Name, err) + } + // Remove Volume Group - vgArgs := []string{vg.Name} - _, err := e.ExecuteCommandWithOutputAsHost(vgRemoveCmd, vgArgs...) + vgArgs = []string{vg.Name} + _, err = hlvm.ExecuteCommandWithOutputAsHost(vgRemoveCmd, vgArgs...) if err != nil { - return fmt.Errorf("failed to remove volume group %s: %w", vg.Name, err) + return fmt.Errorf("failed to remove volume group %q: %w", vg.Name, err) } // Remove physical volumes - pvArgs := make([]string, len(vg.PVs)) - for i := range vg.PVs { - pvArgs[i] = vg.PVs[i].PvName + pvArgs := []string{} + for _, pv := range vg.PVs { + pvArgs = append(pvArgs, pv.PvName) } - _, err = e.ExecuteCommandWithOutputAsHost(pvRemoveCmd, pvArgs...) + _, err = hlvm.ExecuteCommandWithOutputAsHost(pvRemoveCmd, pvArgs...) if err != nil { - return fmt.Errorf("failed to remove physical volumes for the volume group %s: %w", vg.Name, err) + return fmt.Errorf("failed to remove physical volumes for the volume group %q: %w", vg.Name, err) } for _, pv := range vg.PVs { - _, err = e.ExecuteCommandWithOutput(lvmDevicesCmd, "--delpvid", pv.UUID) + _, err = hlvm.ExecuteCommandWithOutput(lvmDevicesCmd, "--delpvid", pv.UUID) if err != nil { var exitError *osexec.ExitError if errors.As(err, &exitError) { @@ -247,18 +247,18 @@ func (vg VolumeGroup) Delete(e exec.Executor) error { } // GetVolumeGroup returns a volume group along with the associated physical volumes -func GetVolumeGroup(exec exec.Executor, name string) (*VolumeGroup, error) { - res := new(vgsOutput) +func (hlvm *HostLVM) GetVG(name string) (VolumeGroup, error) { + res := new(VGReport) args := []string{ "vgs", "--units", "g", "--reportformat", "json", } - if err := execute(exec, res, args...); err != nil { - return nil, fmt.Errorf("failed to list volume groups. %v", err) + if err := hlvm.execute(res, args...); err != nil { + return VolumeGroup{}, fmt.Errorf("failed to list volume groups. %v", err) } vgFound := false - volumeGroup := &VolumeGroup{} + volumeGroup := VolumeGroup{} for _, report := range res.Report { for _, vg := range report.Vg { if vg.Name == name { @@ -271,13 +271,13 @@ func GetVolumeGroup(exec exec.Executor, name string) (*VolumeGroup, error) { } if !vgFound { - return nil, ErrVolumeGroupNotFound + return VolumeGroup{}, ErrVolumeGroupNotFound } // Get Physical Volumes associated with the Volume Group - pvs, err := ListPhysicalVolumes(exec, name) + pvs, err := hlvm.ListPVs(name) if err != nil { - return nil, fmt.Errorf("failed to list physical volumes for volume group %q. %v", name, err) + return VolumeGroup{}, fmt.Errorf("failed to list physical volumes for volume group %q. %v", name, err) } volumeGroup.PVs = pvs @@ -285,20 +285,19 @@ func GetVolumeGroup(exec exec.Executor, name string) (*VolumeGroup, error) { } // ListPhysicalVolumes returns list of physical volumes used to create the given volume group -func ListPhysicalVolumes(exec exec.Executor, vgName string) ([]PhysicalVolume, error) { - res := new(pvsOutput) +func (hlvm *HostLVM) ListPVs(vgName string) ([]PhysicalVolume, error) { + res := new(PVReport) args := []string{ "pvs", "--units", "g", "-v", "--reportformat", "json", } if vgName != "" { args = append(args, "-S", fmt.Sprintf("vgname=%s", vgName)) } - - if err := execute(exec, res, args...); err != nil { - return nil, err + if err := hlvm.execute(res, args...); err != nil { + return []PhysicalVolume{}, err } - pvs := []PhysicalVolume{} + var pvs []PhysicalVolume for _, report := range res.Report { for _, pv := range report.Pv { pvs = append(pvs, PhysicalVolume{ @@ -317,17 +316,14 @@ func ListPhysicalVolumes(exec exec.Executor, vgName string) ([]PhysicalVolume, e } // ListVolumeGroups lists all volume groups and the physical volumes associated with them. -func ListVolumeGroups(exec exec.Executor) ([]VolumeGroup, error) { - res := new(vgsOutput) - args := []string{ - "vgs", "--reportformat", "json", - } +func (hlvm *HostLVM) ListVGs() ([]VolumeGroup, error) { + res := new(VGReport) - if err := execute(exec, res, args...); err != nil { + if err := hlvm.execute(res, "vgs", "--reportformat", "json"); err != nil { return nil, fmt.Errorf("failed to list volume groups. %v", err) } - vgList := []VolumeGroup{} + var vgList []VolumeGroup for _, report := range res.Report { for _, vg := range report.Vg { vgList = append(vgList, VolumeGroup{Name: vg.Name, PVs: []PhysicalVolume{}}) @@ -336,7 +332,7 @@ func ListVolumeGroups(exec exec.Executor) ([]VolumeGroup, error) { // Get Physical Volumes associated with the Volume Group for i, vg := range vgList { - pvs, err := ListPhysicalVolumes(exec, vg.Name) + pvs, err := hlvm.ListPVs(vg.Name) if err != nil { return nil, fmt.Errorf("failed to list physical volumes for volume group %q. %v", vg.Name, err) } @@ -347,8 +343,8 @@ func ListVolumeGroups(exec exec.Executor) ([]VolumeGroup, error) { } // ListLogicalVolumes returns list of logical volumes for a volume group -func ListLogicalVolumes(exec exec.Executor, vgName string) ([]string, error) { - res, err := GetLVSOutput(exec, vgName) +func (hlvm *HostLVM) ListLVsByName(vgName string) ([]string, error) { + res, err := hlvm.ListLVs(vgName) if err != nil { return []string{}, err } @@ -362,13 +358,13 @@ func ListLogicalVolumes(exec exec.Executor, vgName string) ([]string, error) { return lvs, nil } -// GetLVSOutput returns the output for `lvs` command in json format -func GetLVSOutput(exec exec.Executor, vgName string) (*lvsOutput, error) { - res := new(lvsOutput) +// LVReport returns the output for `lvs` command in json format +func (hlvm *HostLVM) ListLVs(vgName string) (*LVReport, error) { + res := new(LVReport) args := []string{ "lvs", "-S", fmt.Sprintf("vgname=%s", vgName), "--units", "g", "--reportformat", "json", } - if err := execute(exec, res, args...); err != nil { + if err := hlvm.execute(res, args...); err != nil { return nil, err } @@ -376,8 +372,8 @@ func GetLVSOutput(exec exec.Executor, vgName string) (*lvsOutput, error) { } // LVExists checks if a logical volume exists in a volume group -func LVExists(exec exec.Executor, lvName, vgName string) (bool, error) { - lvs, err := ListLogicalVolumes(exec, vgName) +func (hlvm *HostLVM) LVExists(lvName, vgName string) (bool, error) { + lvs, err := hlvm.ListLVsByName(vgName) if err != nil { return false, err } @@ -392,30 +388,31 @@ func LVExists(exec exec.Executor, lvName, vgName string) (bool, error) { } // DeleteLV deactivates the logical volume and deletes it -func DeleteLV(exec exec.Executor, lvName, vgName string) error { +func (hlvm *HostLVM) DeleteLV(lvName, vgName string) error { lv := fmt.Sprintf("%s/%s", vgName, lvName) // deactivate logical volume - _, err := exec.ExecuteCommandWithOutputAsHost(lvChangeCmd, "-an", lv) + _, err := hlvm.ExecuteCommandWithOutputAsHost(lvChangeCmd, "-an", lv) if err != nil { - return fmt.Errorf("failed to deactivate thin pool %q in volume group %q. %v", lvName, vgName, err) + return fmt.Errorf("failed to deactivate thin pool %q in volume group %q. %w", lvName, vgName, err) } // delete logical volume - _, err = exec.ExecuteCommandWithOutputAsHost(lvRemoveCmd, lv) + _, err = hlvm.ExecuteCommandWithOutputAsHost(lvRemoveCmd, lv) if err != nil { - return fmt.Errorf("failed to delete logical volume %q in volume group %q. %v", lvName, vgName, err) + return fmt.Errorf("failed to delete logical volume %q in volume group %q. %w", lvName, vgName, err) } return nil } // CreateLV creates the logical volume -func CreateLV(exec exec.Executor, lvName, vgName string, sizePercent int) error { +func (hlvm *HostLVM) CreateLV(lvName, vgName string, sizePercent int) error { + args := []string{"-l", fmt.Sprintf("%d%%FREE", sizePercent), - "-c", defaultChunkSize, "-Z", "y", "-T", fmt.Sprintf("%s/%s", vgName, lvName)} + "-c", DefaultChunkSize, "-Z", "y", "-T", fmt.Sprintf("%s/%s", vgName, lvName)} - if _, err := exec.ExecuteCommandWithOutputAsHost(lvCreateCmd, args...); err != nil { + if _, err := hlvm.ExecuteCommandWithOutputAsHost(lvCreateCmd, args...); err != nil { return fmt.Errorf("failed to create logical volume %q in the volume group %q using command '%s': %w", lvName, vgName, fmt.Sprintf("%s %s", lvCreateCmd, strings.Join(args, " ")), err) } @@ -424,10 +421,11 @@ func CreateLV(exec exec.Executor, lvName, vgName string, sizePercent int) error } // ExtendLV extends the logical volume -func ExtendLV(exec exec.Executor, lvName, vgName string, sizePercent int) error { +func (hlvm *HostLVM) ExtendLV(lvName, vgName string, sizePercent int) error { + args := []string{"-l", fmt.Sprintf("%d%%Vg", sizePercent), fmt.Sprintf("%s/%s", vgName, lvName)} - if _, err := exec.ExecuteCommandWithOutputAsHost(lvExtendCmd, args...); err != nil { + if _, err := hlvm.ExecuteCommandWithOutputAsHost(lvExtendCmd, args...); err != nil { return fmt.Errorf("failed to extend logical volume %q in the volume group %q using command '%s': %w", lvName, vgName, fmt.Sprintf("%s %s", lvExtendCmd, strings.Join(args, " ")), err) } @@ -435,8 +433,8 @@ func ExtendLV(exec exec.Executor, lvName, vgName string, sizePercent int) error return nil } -func execute(exec exec.Executor, v interface{}, args ...string) error { - output, err := exec.ExecuteCommandWithOutputAsHost(lvmCmd, args...) +func (hlvm *HostLVM) execute(v interface{}, args ...string) error { + output, err := hlvm.ExecuteCommandWithOutputAsHost(lvmCmd, args...) if err != nil { return fmt.Errorf("failed to execute command. %v", err) } diff --git a/pkg/lvm/lvm_test.go b/pkg/lvm/lvm_test.go index 1faa42688..f46da6aca 100644 --- a/pkg/lvm/lvm_test.go +++ b/pkg/lvm/lvm_test.go @@ -93,7 +93,7 @@ func TestGetVolumeGroup(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - vg, err := GetVolumeGroup(executor, tt.vgName) + vg, err := NewHostLVM(executor).GetVG(tt.vgName) if tt.wantErr { assert.Error(t, err) } else { @@ -131,7 +131,7 @@ func TestListVolumeGroup(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - vgs, err := ListVolumeGroups(executor) + vgs, err := NewHostLVM(executor).ListVGs() if tt.wantErr { assert.Error(t, err) } else { @@ -151,13 +151,12 @@ func TestListVolumeGroup(t *testing.T) { func TestCreateVolumeGroup(t *testing.T) { tests := []struct { name string - volumeGroup *VolumeGroup - pvs []string + volumeGroup VolumeGroup wantErr bool }{ - {"No Volume Group Name", &VolumeGroup{}, []string{}, true}, - {"No Physical Volumes", &VolumeGroup{Name: "vg1"}, []string{}, true}, - {"Volume Group created successfully", &VolumeGroup{Name: "vg1"}, []string{"/dev/sdb"}, false}, + {"No Volume Group Name", VolumeGroup{}, true}, + {"No Physical Volumes", VolumeGroup{Name: "vg1"}, true}, + {"Volume Group created successfully", VolumeGroup{Name: "vg1", PVs: []PhysicalVolume{{PvName: "/dev/sdb"}}}, false}, } executor := &mockExec.MockExecutor{ @@ -168,7 +167,7 @@ func TestCreateVolumeGroup(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.volumeGroup.Create(executor, tt.pvs) + err := NewHostLVM(executor).CreateVG(tt.volumeGroup) if tt.wantErr { assert.Error(t, err) } else { @@ -181,12 +180,12 @@ func TestCreateVolumeGroup(t *testing.T) { func TestExtendVolumeGroup(t *testing.T) { tests := []struct { name string - volumeGroup *VolumeGroup + volumeGroup VolumeGroup PVs []string wantErr bool }{ - {"No PVs are available", &VolumeGroup{Name: "vg1"}, []string{}, true}, - {"New PVs are available", &VolumeGroup{Name: "vg1"}, []string{"/dev/sdb", "/dev/sdc"}, false}, + {"No PVs are available", VolumeGroup{Name: "vg1"}, []string{}, true}, + {"New PVs are available", VolumeGroup{Name: "vg1"}, []string{"/dev/sdb", "/dev/sdc"}, false}, } executor := &mockExec.MockExecutor{ @@ -197,12 +196,17 @@ func TestExtendVolumeGroup(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.volumeGroup.Extend(executor, tt.PVs) + newVG, err := NewHostLVM(executor).ExtendVG(tt.volumeGroup, tt.PVs) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } + newPVs := make([]string, len(newVG.PVs)) + for i, pv := range newVG.PVs { + newPVs[i] = pv.PvName + } + assert.ElementsMatch(t, newPVs, tt.PVs) }) } } diff --git a/pkg/lvm/mocks/mock_lvm.go b/pkg/lvm/mocks/mock_lvm.go new file mode 100644 index 000000000..628fea83d --- /dev/null +++ b/pkg/lvm/mocks/mock_lvm.go @@ -0,0 +1,623 @@ +// Code generated by mockery v2.33.2. DO NOT EDIT. + +package lvm + +import ( + lvm "github.com/openshift/lvm-operator/pkg/lvm" + mock "github.com/stretchr/testify/mock" +) + +// MockLVM is an autogenerated mock type for the LVM type +type MockLVM struct { + mock.Mock +} + +type MockLVM_Expecter struct { + mock *mock.Mock +} + +func (_m *MockLVM) EXPECT() *MockLVM_Expecter { + return &MockLVM_Expecter{mock: &_m.Mock} +} + +// CreateLV provides a mock function with given fields: lvName, vgName, sizePercent +func (_m *MockLVM) CreateLV(lvName string, vgName string, sizePercent int) error { + ret := _m.Called(lvName, vgName, sizePercent) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, int) error); ok { + r0 = rf(lvName, vgName, sizePercent) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockLVM_CreateLV_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateLV' +type MockLVM_CreateLV_Call struct { + *mock.Call +} + +// CreateLV is a helper method to define mock.On call +// - lvName string +// - vgName string +// - sizePercent int +func (_e *MockLVM_Expecter) CreateLV(lvName interface{}, vgName interface{}, sizePercent interface{}) *MockLVM_CreateLV_Call { + return &MockLVM_CreateLV_Call{Call: _e.mock.On("CreateLV", lvName, vgName, sizePercent)} +} + +func (_c *MockLVM_CreateLV_Call) Run(run func(lvName string, vgName string, sizePercent int)) *MockLVM_CreateLV_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string), args[2].(int)) + }) + return _c +} + +func (_c *MockLVM_CreateLV_Call) Return(_a0 error) *MockLVM_CreateLV_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLVM_CreateLV_Call) RunAndReturn(run func(string, string, int) error) *MockLVM_CreateLV_Call { + _c.Call.Return(run) + return _c +} + +// CreateVG provides a mock function with given fields: vg +func (_m *MockLVM) CreateVG(vg lvm.VolumeGroup) error { + ret := _m.Called(vg) + + var r0 error + if rf, ok := ret.Get(0).(func(lvm.VolumeGroup) error); ok { + r0 = rf(vg) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockLVM_CreateVG_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateVG' +type MockLVM_CreateVG_Call struct { + *mock.Call +} + +// CreateVG is a helper method to define mock.On call +// - vg lvm.VolumeGroup +func (_e *MockLVM_Expecter) CreateVG(vg interface{}) *MockLVM_CreateVG_Call { + return &MockLVM_CreateVG_Call{Call: _e.mock.On("CreateVG", vg)} +} + +func (_c *MockLVM_CreateVG_Call) Run(run func(vg lvm.VolumeGroup)) *MockLVM_CreateVG_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(lvm.VolumeGroup)) + }) + return _c +} + +func (_c *MockLVM_CreateVG_Call) Return(_a0 error) *MockLVM_CreateVG_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLVM_CreateVG_Call) RunAndReturn(run func(lvm.VolumeGroup) error) *MockLVM_CreateVG_Call { + _c.Call.Return(run) + return _c +} + +// DeleteLV provides a mock function with given fields: lvName, vgName +func (_m *MockLVM) DeleteLV(lvName string, vgName string) error { + ret := _m.Called(lvName, vgName) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(lvName, vgName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockLVM_DeleteLV_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteLV' +type MockLVM_DeleteLV_Call struct { + *mock.Call +} + +// DeleteLV is a helper method to define mock.On call +// - lvName string +// - vgName string +func (_e *MockLVM_Expecter) DeleteLV(lvName interface{}, vgName interface{}) *MockLVM_DeleteLV_Call { + return &MockLVM_DeleteLV_Call{Call: _e.mock.On("DeleteLV", lvName, vgName)} +} + +func (_c *MockLVM_DeleteLV_Call) Run(run func(lvName string, vgName string)) *MockLVM_DeleteLV_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string)) + }) + return _c +} + +func (_c *MockLVM_DeleteLV_Call) Return(_a0 error) *MockLVM_DeleteLV_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLVM_DeleteLV_Call) RunAndReturn(run func(string, string) error) *MockLVM_DeleteLV_Call { + _c.Call.Return(run) + return _c +} + +// DeleteVG provides a mock function with given fields: vg +func (_m *MockLVM) DeleteVG(vg lvm.VolumeGroup) error { + ret := _m.Called(vg) + + var r0 error + if rf, ok := ret.Get(0).(func(lvm.VolumeGroup) error); ok { + r0 = rf(vg) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockLVM_DeleteVG_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteVG' +type MockLVM_DeleteVG_Call struct { + *mock.Call +} + +// DeleteVG is a helper method to define mock.On call +// - vg lvm.VolumeGroup +func (_e *MockLVM_Expecter) DeleteVG(vg interface{}) *MockLVM_DeleteVG_Call { + return &MockLVM_DeleteVG_Call{Call: _e.mock.On("DeleteVG", vg)} +} + +func (_c *MockLVM_DeleteVG_Call) Run(run func(vg lvm.VolumeGroup)) *MockLVM_DeleteVG_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(lvm.VolumeGroup)) + }) + return _c +} + +func (_c *MockLVM_DeleteVG_Call) Return(_a0 error) *MockLVM_DeleteVG_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLVM_DeleteVG_Call) RunAndReturn(run func(lvm.VolumeGroup) error) *MockLVM_DeleteVG_Call { + _c.Call.Return(run) + return _c +} + +// ExtendLV provides a mock function with given fields: lvName, vgName, sizePercent +func (_m *MockLVM) ExtendLV(lvName string, vgName string, sizePercent int) error { + ret := _m.Called(lvName, vgName, sizePercent) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, int) error); ok { + r0 = rf(lvName, vgName, sizePercent) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockLVM_ExtendLV_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExtendLV' +type MockLVM_ExtendLV_Call struct { + *mock.Call +} + +// ExtendLV is a helper method to define mock.On call +// - lvName string +// - vgName string +// - sizePercent int +func (_e *MockLVM_Expecter) ExtendLV(lvName interface{}, vgName interface{}, sizePercent interface{}) *MockLVM_ExtendLV_Call { + return &MockLVM_ExtendLV_Call{Call: _e.mock.On("ExtendLV", lvName, vgName, sizePercent)} +} + +func (_c *MockLVM_ExtendLV_Call) Run(run func(lvName string, vgName string, sizePercent int)) *MockLVM_ExtendLV_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string), args[2].(int)) + }) + return _c +} + +func (_c *MockLVM_ExtendLV_Call) Return(_a0 error) *MockLVM_ExtendLV_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLVM_ExtendLV_Call) RunAndReturn(run func(string, string, int) error) *MockLVM_ExtendLV_Call { + _c.Call.Return(run) + return _c +} + +// ExtendVG provides a mock function with given fields: vg, pvs +func (_m *MockLVM) ExtendVG(vg lvm.VolumeGroup, pvs []string) (lvm.VolumeGroup, error) { + ret := _m.Called(vg, pvs) + + var r0 lvm.VolumeGroup + var r1 error + if rf, ok := ret.Get(0).(func(lvm.VolumeGroup, []string) (lvm.VolumeGroup, error)); ok { + return rf(vg, pvs) + } + if rf, ok := ret.Get(0).(func(lvm.VolumeGroup, []string) lvm.VolumeGroup); ok { + r0 = rf(vg, pvs) + } else { + r0 = ret.Get(0).(lvm.VolumeGroup) + } + + if rf, ok := ret.Get(1).(func(lvm.VolumeGroup, []string) error); ok { + r1 = rf(vg, pvs) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLVM_ExtendVG_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExtendVG' +type MockLVM_ExtendVG_Call struct { + *mock.Call +} + +// ExtendVG is a helper method to define mock.On call +// - vg lvm.VolumeGroup +// - pvs []string +func (_e *MockLVM_Expecter) ExtendVG(vg interface{}, pvs interface{}) *MockLVM_ExtendVG_Call { + return &MockLVM_ExtendVG_Call{Call: _e.mock.On("ExtendVG", vg, pvs)} +} + +func (_c *MockLVM_ExtendVG_Call) Run(run func(vg lvm.VolumeGroup, pvs []string)) *MockLVM_ExtendVG_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(lvm.VolumeGroup), args[1].([]string)) + }) + return _c +} + +func (_c *MockLVM_ExtendVG_Call) Return(_a0 lvm.VolumeGroup, _a1 error) *MockLVM_ExtendVG_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLVM_ExtendVG_Call) RunAndReturn(run func(lvm.VolumeGroup, []string) (lvm.VolumeGroup, error)) *MockLVM_ExtendVG_Call { + _c.Call.Return(run) + return _c +} + +// GetVG provides a mock function with given fields: name +func (_m *MockLVM) GetVG(name string) (lvm.VolumeGroup, error) { + ret := _m.Called(name) + + var r0 lvm.VolumeGroup + var r1 error + if rf, ok := ret.Get(0).(func(string) (lvm.VolumeGroup, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) lvm.VolumeGroup); ok { + r0 = rf(name) + } else { + r0 = ret.Get(0).(lvm.VolumeGroup) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLVM_GetVG_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetVG' +type MockLVM_GetVG_Call struct { + *mock.Call +} + +// GetVG is a helper method to define mock.On call +// - name string +func (_e *MockLVM_Expecter) GetVG(name interface{}) *MockLVM_GetVG_Call { + return &MockLVM_GetVG_Call{Call: _e.mock.On("GetVG", name)} +} + +func (_c *MockLVM_GetVG_Call) Run(run func(name string)) *MockLVM_GetVG_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLVM_GetVG_Call) Return(_a0 lvm.VolumeGroup, _a1 error) *MockLVM_GetVG_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLVM_GetVG_Call) RunAndReturn(run func(string) (lvm.VolumeGroup, error)) *MockLVM_GetVG_Call { + _c.Call.Return(run) + return _c +} + +// LVExists provides a mock function with given fields: lvName, vgName +func (_m *MockLVM) LVExists(lvName string, vgName string) (bool, error) { + ret := _m.Called(lvName, vgName) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(string, string) (bool, error)); ok { + return rf(lvName, vgName) + } + if rf, ok := ret.Get(0).(func(string, string) bool); ok { + r0 = rf(lvName, vgName) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(lvName, vgName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLVM_LVExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LVExists' +type MockLVM_LVExists_Call struct { + *mock.Call +} + +// LVExists is a helper method to define mock.On call +// - lvName string +// - vgName string +func (_e *MockLVM_Expecter) LVExists(lvName interface{}, vgName interface{}) *MockLVM_LVExists_Call { + return &MockLVM_LVExists_Call{Call: _e.mock.On("LVExists", lvName, vgName)} +} + +func (_c *MockLVM_LVExists_Call) Run(run func(lvName string, vgName string)) *MockLVM_LVExists_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string)) + }) + return _c +} + +func (_c *MockLVM_LVExists_Call) Return(_a0 bool, _a1 error) *MockLVM_LVExists_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLVM_LVExists_Call) RunAndReturn(run func(string, string) (bool, error)) *MockLVM_LVExists_Call { + _c.Call.Return(run) + return _c +} + +// ListLVs provides a mock function with given fields: vgName +func (_m *MockLVM) ListLVs(vgName string) (*lvm.LVReport, error) { + ret := _m.Called(vgName) + + var r0 *lvm.LVReport + var r1 error + if rf, ok := ret.Get(0).(func(string) (*lvm.LVReport, error)); ok { + return rf(vgName) + } + if rf, ok := ret.Get(0).(func(string) *lvm.LVReport); ok { + r0 = rf(vgName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*lvm.LVReport) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(vgName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLVM_ListLVs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListLVs' +type MockLVM_ListLVs_Call struct { + *mock.Call +} + +// ListLVs is a helper method to define mock.On call +// - vgName string +func (_e *MockLVM_Expecter) ListLVs(vgName interface{}) *MockLVM_ListLVs_Call { + return &MockLVM_ListLVs_Call{Call: _e.mock.On("ListLVs", vgName)} +} + +func (_c *MockLVM_ListLVs_Call) Run(run func(vgName string)) *MockLVM_ListLVs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLVM_ListLVs_Call) Return(_a0 *lvm.LVReport, _a1 error) *MockLVM_ListLVs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLVM_ListLVs_Call) RunAndReturn(run func(string) (*lvm.LVReport, error)) *MockLVM_ListLVs_Call { + _c.Call.Return(run) + return _c +} + +// ListLVsByName provides a mock function with given fields: vgName +func (_m *MockLVM) ListLVsByName(vgName string) ([]string, error) { + ret := _m.Called(vgName) + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { + return rf(vgName) + } + if rf, ok := ret.Get(0).(func(string) []string); ok { + r0 = rf(vgName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(vgName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLVM_ListLVsByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListLVsByName' +type MockLVM_ListLVsByName_Call struct { + *mock.Call +} + +// ListLVsByName is a helper method to define mock.On call +// - vgName string +func (_e *MockLVM_Expecter) ListLVsByName(vgName interface{}) *MockLVM_ListLVsByName_Call { + return &MockLVM_ListLVsByName_Call{Call: _e.mock.On("ListLVsByName", vgName)} +} + +func (_c *MockLVM_ListLVsByName_Call) Run(run func(vgName string)) *MockLVM_ListLVsByName_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLVM_ListLVsByName_Call) Return(_a0 []string, _a1 error) *MockLVM_ListLVsByName_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLVM_ListLVsByName_Call) RunAndReturn(run func(string) ([]string, error)) *MockLVM_ListLVsByName_Call { + _c.Call.Return(run) + return _c +} + +// ListPVs provides a mock function with given fields: vgName +func (_m *MockLVM) ListPVs(vgName string) ([]lvm.PhysicalVolume, error) { + ret := _m.Called(vgName) + + var r0 []lvm.PhysicalVolume + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]lvm.PhysicalVolume, error)); ok { + return rf(vgName) + } + if rf, ok := ret.Get(0).(func(string) []lvm.PhysicalVolume); ok { + r0 = rf(vgName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]lvm.PhysicalVolume) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(vgName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLVM_ListPVs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPVs' +type MockLVM_ListPVs_Call struct { + *mock.Call +} + +// ListPVs is a helper method to define mock.On call +// - vgName string +func (_e *MockLVM_Expecter) ListPVs(vgName interface{}) *MockLVM_ListPVs_Call { + return &MockLVM_ListPVs_Call{Call: _e.mock.On("ListPVs", vgName)} +} + +func (_c *MockLVM_ListPVs_Call) Run(run func(vgName string)) *MockLVM_ListPVs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLVM_ListPVs_Call) Return(_a0 []lvm.PhysicalVolume, _a1 error) *MockLVM_ListPVs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLVM_ListPVs_Call) RunAndReturn(run func(string) ([]lvm.PhysicalVolume, error)) *MockLVM_ListPVs_Call { + _c.Call.Return(run) + return _c +} + +// ListVGs provides a mock function with given fields: +func (_m *MockLVM) ListVGs() ([]lvm.VolumeGroup, error) { + ret := _m.Called() + + var r0 []lvm.VolumeGroup + var r1 error + if rf, ok := ret.Get(0).(func() ([]lvm.VolumeGroup, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []lvm.VolumeGroup); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]lvm.VolumeGroup) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockLVM_ListVGs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListVGs' +type MockLVM_ListVGs_Call struct { + *mock.Call +} + +// ListVGs is a helper method to define mock.On call +func (_e *MockLVM_Expecter) ListVGs() *MockLVM_ListVGs_Call { + return &MockLVM_ListVGs_Call{Call: _e.mock.On("ListVGs")} +} + +func (_c *MockLVM_ListVGs_Call) Run(run func()) *MockLVM_ListVGs_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockLVM_ListVGs_Call) Return(_a0 []lvm.VolumeGroup, _a1 error) *MockLVM_ListVGs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockLVM_ListVGs_Call) RunAndReturn(run func() ([]lvm.VolumeGroup, error)) *MockLVM_ListVGs_Call { + _c.Call.Return(run) + return _c +} + +// NewMockLVM creates a new instance of MockLVM. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockLVM(t interface { + mock.TestingT + Cleanup(func()) +}) *MockLVM { + mock := &MockLVM{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/vgmanager/devices.go b/pkg/vgmanager/devices.go index 81298c9bf..9895a4432 100644 --- a/pkg/vgmanager/devices.go +++ b/pkg/vgmanager/devices.go @@ -55,7 +55,7 @@ func (r *VGReconciler) addDevicesToVG(ctx context.Context, vgs []lvm.VolumeGroup if existingVolumeGroup != nil { logger.Info("extending an existing volume group", "VGName", vgName) - if _, err := existingVolumeGroup.ExtendVG(r.executor, args); err != nil { + if _, err := r.LVM.ExtendVG(*existingVolumeGroup, args); err != nil { return fmt.Errorf("failed to extend volume group %s: %w", vgName, err) } } else { @@ -64,11 +64,10 @@ func (r *VGReconciler) addDevicesToVG(ctx context.Context, vgs []lvm.VolumeGroup for _, pvName := range args { pvs = append(pvs, lvm.PhysicalVolume{PvName: pvName}) } - if err := (lvm.VolumeGroup{Name: vgName, PVs: pvs}).CreateVG(r.executor); err != nil { + if err := r.LVM.CreateVG(lvm.VolumeGroup{Name: vgName, PVs: pvs}); err != nil { return fmt.Errorf("failed to create volume group %s: %w", vgName, err) } } - return nil } @@ -99,14 +98,14 @@ DeviceLoop: } logger = logger.WithValues("Device.Name", blockDevice.Name) - for name, filterFunc := range r.Filters(r.LSBLK) { - logger := logger.WithValues("filterFunc.Name", name) - valid, err := filterFunc(blockDevice, r.executor) + for name, filter := range r.Filters(r.LVM, r.LSBLK) { + logger := logger.WithValues("filter.Name", name) + valid, err := filter(blockDevice) if err != nil { - logger.Error(err, "filterFunc error") + logger.Error(err, "filter error") continue DeviceLoop } else if !valid { - logger.Info("does not match filterFunc") + logger.Info("does not match filter") continue DeviceLoop } } diff --git a/pkg/vgmanager/devices_test.go b/pkg/vgmanager/devices_test.go index b4f0bb826..f94b8be68 100644 --- a/pkg/vgmanager/devices_test.go +++ b/pkg/vgmanager/devices_test.go @@ -34,8 +34,8 @@ func TestAvailableDevicesForVG(t *testing.T) { } r := &VGReconciler{} - r.Filters = func(instance lsblk.LSBLK) filter.Filters { - filters := filter.DefaultFilters(instance) + r.Filters = func(a lvm.LVM, b lsblk.LSBLK) filter.Filters { + filters := filter.DefaultFilters(a, b) // remove noBindMounts filter as it reads `proc/1/mountinfo` file. delete(filters, "noBindMounts") return filters diff --git a/pkg/vgmanager/status.go b/pkg/vgmanager/status.go index b9811019c..d91c6d06b 100644 --- a/pkg/vgmanager/status.go +++ b/pkg/vgmanager/status.go @@ -21,7 +21,6 @@ import ( "fmt" lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" - "github.com/openshift/lvm-operator/pkg/lvm" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -166,7 +165,7 @@ func (r *VGReconciler) removeVolumeGroupStatus(ctx context.Context, vg *lvmv1alp } func (r *VGReconciler) setDevices(status *lvmv1alpha1.VGStatus) (bool, error) { - vgs, err := lvm.ListVolumeGroups(r.executor) + vgs, err := r.LVM.ListVGs() if err != nil { return false, fmt.Errorf("failed to list volume groups. %v", err) } diff --git a/pkg/vgmanager/suite_test.go b/pkg/vgmanager/suite_test.go new file mode 100644 index 000000000..dcf749380 --- /dev/null +++ b/pkg/vgmanager/suite_test.go @@ -0,0 +1,186 @@ +/* +Copyright © 2023 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vgmanager + +import ( + "context" + "log" + "os/user" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + configv1 "github.com/openshift/api/config/v1" + secv1 "github.com/openshift/api/security/v1" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" + + lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" + "github.com/openshift/lvm-operator/pkg/filter" + lsblkmocks "github.com/openshift/lvm-operator/pkg/lsblk/mocks" + lvmmocks "github.com/openshift/lvm-operator/pkg/lvm/mocks" + "github.com/openshift/lvm-operator/pkg/lvmd" + + topolvmv1 "github.com/topolvm/topolvm/api/v1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + testNodeSelector corev1.NodeSelector + testLVMDFile string + mockLSBLK *lsblkmocks.MockLSBLK + mockLVM *lvmmocks.MockLVM +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Controller Suite") +} + +const ( + testNamespaceName = "openshift-storage" + testNodeName = "test-node" + testHostname = "test-host.vgmanager.test.io" + timeout = time.Second * 10 + interval = time.Millisecond * 250 +) + +var _ = BeforeSuite(func() { + logger := zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)) + logf.SetLogger(logger) + + ctx, cancel = context.WithCancel(context.Background()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases"), + filepath.Join("..", "..", "test", "e2e", "testdata")}, + ErrorIfCRDPathMissing: true, + CRDInstallOptions: envtest.CRDInstallOptions{ + CleanUpAfterUse: true, + }, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = lvmv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + err = topolvmv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + err = snapapi.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + err = secv1.Install(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + err = configv1.Install(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + MetricsBindAddress: "0", + }) + Expect(err).ToNot(HaveOccurred()) + + // CreateVG the primary namespace to be used by some tests + testNamespace := &corev1.Namespace{} + testNamespace.SetName(testNamespaceName) + Expect(k8sClient.Create(ctx, testNamespace)).Should(Succeed()) + + testNode := &corev1.Node{} + testNode.SetName(testNodeName) + hostnameKey := "kubernetes.io/hostname" + testNode.SetLabels(map[string]string{ + hostnameKey: testHostname, + }) + testNodeSelector = corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{ + MatchExpressions: []corev1.NodeSelectorRequirement{{ + Key: hostnameKey, + Operator: corev1.NodeSelectorOpIn, + Values: []string{testHostname}, + }}, + }}} + Expect(k8sClient.Create(ctx, testNode)).Should(Succeed()) + + testLVMD := lvmd.NewFileConfigurator(filepath.Join(GinkgoT().TempDir(), "lvmd.yaml")) + mockLSBLK = lsblkmocks.NewMockLSBLK(GinkgoT()) + mockLVM = lvmmocks.NewMockLVM(GinkgoT()) + err = (&VGReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + EventRecorder: k8sManager.GetEventRecorderFor(ControllerName), + LVM: mockLVM, + LSBLK: mockLSBLK, + LVMD: testLVMD, + Namespace: testNamespaceName, + NodeName: testNodeName, + Filters: filter.DefaultFilters, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() + +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + Expect(testEnv.Stop()).To(Succeed()) +}) + +func isRoot() bool { + currentUser, err := user.Current() + if err != nil { + log.Fatalf("[isRoot] Unable to get current user: %s", err) + } + return currentUser.Username == "root" || currentUser.Uid == "0" +} diff --git a/pkg/vgmanager/vgmanager_controller.go b/pkg/vgmanager/vgmanager_controller.go index da19fac84..39dfdeabd 100644 --- a/pkg/vgmanager/vgmanager_controller.go +++ b/pkg/vgmanager/vgmanager_controller.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "strconv" - "strings" "time" "github.com/google/go-cmp/cmp" @@ -28,7 +27,6 @@ import ( lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" "github.com/openshift/lvm-operator/controllers" "github.com/openshift/lvm-operator/pkg/filter" - "github.com/openshift/lvm-operator/pkg/internal/exec" "github.com/openshift/lvm-operator/pkg/lsblk" "github.com/openshift/lvm-operator/pkg/lvm" "github.com/openshift/lvm-operator/pkg/lvmd" @@ -77,7 +75,6 @@ var ( // SetupWithManager sets up the controller with the Manager. func (r *VGReconciler) SetupWithManager(mgr ctrl.Manager) error { - r.executor = &exec.CommandExecutor{} return ctrl.NewControllerManagedBy(mgr). For(&lvmv1alpha1.LVMVolumeGroup{}). Owns(&lvmv1alpha1.LVMVolumeGroupNodeStatus{}, builder.MatchEveryOwner). @@ -86,14 +83,14 @@ func (r *VGReconciler) SetupWithManager(mgr ctrl.Manager) error { type VGReconciler struct { client.Client + Scheme *runtime.Scheme record.EventRecorder - Scheme *runtime.Scheme - executor exec.Executor - LVMD lvmd.Configurator + LVMD lvmd.Configurator + lvm.LVM lsblk.LSBLK NodeName string Namespace string - Filters func(lsblk.LSBLK) filter.Filters + Filters func(lvm.LVM, lsblk.LSBLK) filter.Filters } func (r *VGReconciler) getFinalizer() string { @@ -101,8 +98,8 @@ func (r *VGReconciler) getFinalizer() string { } func (r *VGReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) - logger.Info("reconciling", "LVMVolumeGroup", req) + logger := log.FromContext(ctx).WithValues("LVMVolumeGroup", req) + logger.Info("reconciling") // Check if this LVMVolumeGroup needs to be processed on this node volumeGroup := &lvmv1alpha1.LVMVolumeGroup{} @@ -131,6 +128,7 @@ func (r *VGReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re return ctrl.Result{}, fmt.Errorf("could not determine if LVMVolumeGroupNodeStatus still needs to be created: %w", err) } } + return r.reconcile(ctx, volumeGroup) } @@ -166,12 +164,12 @@ func (r *VGReconciler) reconcile(ctx context.Context, volumeGroup *lvmv1alpha1.L } existingLvmdConfig := *lvmdConfig - vgs, err := lvm.ListVolumeGroups(r.executor) + vgs, err := r.LVM.ListVGs() if err != nil { return ctrl.Result{}, fmt.Errorf("failed to list volume groups: %w", err) } - blockDevices, err := r.ListBlockDevices() + blockDevices, err := r.LSBLK.ListBlockDevices() if err != nil { return ctrl.Result{}, fmt.Errorf("failed to list block devices: %w", err) } @@ -226,7 +224,7 @@ func (r *VGReconciler) reconcile(ctx context.Context, volumeGroup *lvmv1alpha1.L return reconcileAgain, nil } - // Create/extend VG + // Create VG/extend VG if err = r.addDevicesToVG(ctx, vgs, volumeGroup.Name, availableDevices); err != nil { err = fmt.Errorf("failed to create/extend volume group %s: %w", volumeGroup.Name, err) r.WarningEvent(ctx, volumeGroup, EventReasonErrorVGCreateOrExtendFailed, err) @@ -305,6 +303,7 @@ func (r *VGReconciler) reconcile(ctx context.Context, volumeGroup *lvmv1alpha1.L func (r *VGReconciler) processDelete(ctx context.Context, volumeGroup *lvmv1alpha1.LVMVolumeGroup) error { logger := log.FromContext(ctx).WithValues("VGName", volumeGroup.Name) + logger.Info("deleting") // Read the lvmd config file lvmdConfig, err := r.LVMD.Load() @@ -330,7 +329,7 @@ func (r *VGReconciler) processDelete(ctx context.Context, volumeGroup *lvmv1alph } // Check if volume group exists - vg, err := lvm.GetVolumeGroup(r.executor, volumeGroup.Name) + vg, err := r.LVM.GetVG(volumeGroup.Name) if err != nil { if err != lvm.ErrVolumeGroupNotFound { return fmt.Errorf("failed to get volume group %s, %w", volumeGroup.GetName(), err) @@ -341,14 +340,13 @@ func (r *VGReconciler) processDelete(ctx context.Context, volumeGroup *lvmv1alph if volumeGroup.Spec.ThinPoolConfig != nil { thinPoolName := volumeGroup.Spec.ThinPoolConfig.Name logger := logger.WithValues("ThinPool", thinPoolName) - - thinPoolExists, err := lvm.LVExists(r.executor, thinPoolName, volumeGroup.Name) + thinPoolExists, err := r.LVM.LVExists(thinPoolName, volumeGroup.Name) if err != nil { return fmt.Errorf("failed to check existence of thin pool %q in volume group %q. %v", thinPoolName, volumeGroup.Name, err) } if thinPoolExists { - if err := lvm.DeleteLV(r.executor, thinPoolName, volumeGroup.Name); err != nil { + if err := r.LVM.DeleteLV(thinPoolName, volumeGroup.Name); err != nil { err := fmt.Errorf("failed to delete thin pool %s in volume group %s: %w", thinPoolName, volumeGroup.Name, err) if err := r.setVolumeGroupFailedStatus(ctx, volumeGroup, err); err != nil { logger.Error(err, "failed to set status to failed") @@ -361,7 +359,7 @@ func (r *VGReconciler) processDelete(ctx context.Context, volumeGroup *lvmv1alph } } - if err = vg.Delete(r.executor); err != nil { + if err = r.LVM.DeleteVG(vg); err != nil { err := fmt.Errorf("failed to delete volume group %s: %w", volumeGroup.Name, err) if err := r.setVolumeGroupFailedStatus(ctx, volumeGroup, err); err != nil { logger.Error(err, "failed to set status to failed", "VGName", volumeGroup.GetName()) @@ -400,7 +398,6 @@ func (r *VGReconciler) processDelete(ctx context.Context, volumeGroup *lvmv1alph logger.Info("removing finalizer") return r.Client.Update(ctx, volumeGroup) } - return nil } @@ -414,7 +411,7 @@ func (r *VGReconciler) validateLVs(ctx context.Context, volumeGroup *lvmv1alpha1 return nil } - resp, err := lvm.GetLVSOutput(r.executor, volumeGroup.Name) + resp, err := r.LVM.ListLVs(volumeGroup.Name) if err != nil { return fmt.Errorf("could not get logical volumes found inside volume group, volume group content is degraded or corrupt: %w", err) } @@ -470,7 +467,7 @@ func (r *VGReconciler) validateLVs(ctx context.Context, volumeGroup *lvmv1alpha1 func (r *VGReconciler) addThinPoolToVG(ctx context.Context, vgName string, config *lvmv1alpha1.ThinPoolConfig) error { logger := log.FromContext(ctx).WithValues("VGName", vgName, "ThinPool", config.Name) - resp, err := lvm.GetLVSOutput(r.executor, vgName) + resp, err := r.LVM.ListLVs(vgName) if err != nil { return fmt.Errorf("failed to list logical volumes in the volume group %q. %v", vgName, err) } @@ -478,7 +475,11 @@ func (r *VGReconciler) addThinPoolToVG(ctx context.Context, vgName string, confi for _, report := range resp.Report { for _, lv := range report.Lv { if lv.Name == config.Name { - if strings.Contains(lv.LvAttr, "t") { + lvAttr, err := ParsedLvAttr(lv.LvAttr) + if err != nil { + return fmt.Errorf("could not parse lvattr to determine if thin pool exists: %w", err) + } + if lvAttr.VolumeType == VolumeTypeThinPool { logger.Info("lvm thinpool already exists") if err := r.extendThinPool(ctx, vgName, lv.LvSize, config); err != nil { return fmt.Errorf("failed to extend the lvm thinpool %s in volume group %s: %w", config.Name, vgName, err) @@ -486,13 +487,13 @@ func (r *VGReconciler) addThinPoolToVG(ctx context.Context, vgName string, confi return nil } - return fmt.Errorf("failed to create thin pool %q, logical volume with same name already exists", config.Name) + return fmt.Errorf("failed to create thin pool %q, logical volume with same name already exists, but cannot be extended as its not a thinpool (%s)", config.Name, lvAttr) } } } logger.Info("creating lvm thinpool") - if err := lvm.CreateLV(r.executor, config.Name, vgName, config.SizePercent); err != nil { + if err := r.LVM.CreateLV(config.Name, vgName, config.SizePercent); err != nil { return fmt.Errorf("failed to create thinpool: %w", err) } logger.Info("successfully created thinpool") @@ -503,7 +504,7 @@ func (r *VGReconciler) addThinPoolToVG(ctx context.Context, vgName string, confi func (r *VGReconciler) extendThinPool(ctx context.Context, vgName string, lvSize string, config *lvmv1alpha1.ThinPoolConfig) error { logger := log.FromContext(ctx).WithValues("VGName", vgName, "ThinPool", config.Name) - vg, err := lvm.GetVolumeGroup(r.executor, vgName) + vg, err := r.LVM.GetVG(vgName) if err != nil { if err != lvm.ErrVolumeGroupNotFound { return fmt.Errorf("failed to get volume group. %q, %v", vgName, err) @@ -526,8 +527,8 @@ func (r *VGReconciler) extendThinPool(ctx context.Context, vgName string, lvSize return nil } - logger.Info("extending lvm thinpool ") - if err := lvm.ExtendLV(r.executor, config.Name, vgName, config.SizePercent); err != nil { + logger.Info("extending lvm thinpool") + if err := r.LVM.ExtendLV(config.Name, vgName, config.SizePercent); err != nil { return fmt.Errorf("failed to extend thinpool: %w", err) } logger.Info("successfully extended thinpool") @@ -535,6 +536,23 @@ func (r *VGReconciler) extendThinPool(ctx context.Context, vgName string, lvSize return nil } +func (r *VGReconciler) matchesThisNode(ctx context.Context, selector *corev1.NodeSelector) (bool, error) { + node := &corev1.Node{} + err := r.Client.Get(ctx, types.NamespacedName{Name: r.NodeName}, node) + if err != nil { + return false, err + } + if selector == nil { + return true, nil + } + if node == nil { + return false, fmt.Errorf("node cannot be nil") + } + + matches, err := corev1helper.MatchNodeSelectorTerms(node, selector) + return matches, err +} + // WarningEvent sends an event to both the nodeStatus, and the affected processed volumeGroup as well as the owning LVMCluster if present func (r *VGReconciler) WarningEvent(ctx context.Context, obj *lvmv1alpha1.LVMVolumeGroup, reason EventReasonError, err error) { nodeStatus := &lvmv1alpha1.LVMVolumeGroupNodeStatus{} @@ -581,24 +599,3 @@ func (r *VGReconciler) NormalEvent(ctx context.Context, obj *lvmv1alpha1.LVMVolu r.Event(obj, corev1.EventTypeNormal, string(reason), fmt.Sprintf("update on node %s: %s", client.ObjectKeyFromObject(nodeStatus), message)) } - -func NodeSelectorMatchesNodeLabels(node *corev1.Node, nodeSelector *corev1.NodeSelector) (bool, error) { - if nodeSelector == nil { - return true, nil - } - if node == nil { - return false, fmt.Errorf("node cannot be nil") - } - - matches, err := corev1helper.MatchNodeSelectorTerms(node, nodeSelector) - return matches, err -} - -func (r *VGReconciler) matchesThisNode(ctx context.Context, selector *corev1.NodeSelector) (bool, error) { - node := &corev1.Node{} - err := r.Client.Get(ctx, types.NamespacedName{Name: r.NodeName}, node) - if err != nil { - return false, err - } - return NodeSelectorMatchesNodeLabels(node, selector) -} diff --git a/pkg/vgmanager/vgmanager_controller_test.go b/pkg/vgmanager/vgmanager_controller_test.go index eb3c4c209..fd30fb078 100644 --- a/pkg/vgmanager/vgmanager_controller_test.go +++ b/pkg/vgmanager/vgmanager_controller_test.go @@ -3,226 +3,134 @@ package vgmanager import ( "context" "fmt" - "testing" + "os" + "path/filepath" - "github.com/go-logr/logr/testr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" - "github.com/openshift/lvm-operator/pkg/internal/exec" - mockExec "github.com/openshift/lvm-operator/pkg/internal/exec/test" - "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/strings/slices" - "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/openshift/lvm-operator/pkg/lsblk" + "github.com/openshift/lvm-operator/pkg/lvm" + "github.com/stretchr/testify/mock" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" ) -var mockLvsOutputNoReportContent = ` -{ - "report": [] -} -` - -var mockLvsOutputNoLVsInReport = ` -{ - "report": [ - { - "lv": [] - } - ] - } -` +var _ = Describe("vgmanager controller", func() { + SetDefaultEventuallyTimeout(timeout) + SetDefaultEventuallyPollingInterval(interval) + Context("verifying standard behavior with node selector", func() { + It("should be reconciled successfully with a mocked block device", testMockedBlockDeviceOnHost) + }) +}) -var mockLvsOutputWrongLVsInReport = ` -{ - "report": [ - { - "lv": [ - {"lv_name":"thin-pool-BLUB", "vg_name":"vg1", "lv_attr":"twi-a-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"10.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} - ] - } - ] - } -` +func testMockedBlockDeviceOnHost(ctx context.Context) { + DeferCleanup(func() { + mockLVM.AssertExpectations(GinkgoT()) + mockLSBLK.AssertExpectations(GinkgoT()) + }) -var mockLvsOutputThinPoolValid = ` -{ - "report": [ - { - "lv": [ - {"lv_name":"thin-pool-1", "vg_name":"vg1", "lv_attr":"twi-a-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"10.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} - ] - } - ] - } -` + By("setting up the disk as a block device with losetup") + device := filepath.Join(GinkgoT().TempDir(), "mock0") + // required create to survive valid device check + _, err := os.Create(device) + Expect(err).To(Succeed()) -var mockLvsOutputThinPoolHighMetadataUse = ` -{ - "report": [ - { - "lv": [ - {"lv_name":"thin-pool-1", "vg_name":"vg1", "lv_attr":"twi-a-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"98.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} - ] - } - ] - } -` -var mockLvsOutputThinPoolSuspended = ` -{ - "report": [ - { - "lv": [ - {"lv_name":"thin-pool-1", "vg_name":"vg1", "lv_attr":"twi-s-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"10.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} - ] - } - ] - } -` + By("setting up the LVMVolumeGroup with the temporary device") + vg := &lvmv1alpha1.LVMVolumeGroup{} + vg.SetName("vg1") + vg.SetNamespace(testNamespaceName) + vg.Spec.NodeSelector = testNodeSelector.DeepCopy() + vg.Spec.DeviceSelector = &lvmv1alpha1.DeviceSelector{Paths: []string{device}} + vg.Spec.ThinPoolConfig = &lvmv1alpha1.ThinPoolConfig{ + Name: "thin-pool", + SizePercent: 90, + OverprovisionRatio: 10, + } -var mockLvsOutputRAID = ` -{ - "report": [ - { - "lv": [ - {"lv_name":"thin-pool-1", "vg_name":"vg1", "lv_attr":"rwi-a-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"10.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} - ] - } - ] - } -` + mockLVM.EXPECT().ListVGs().Return(nil, nil).Once() + mockLSBLK.EXPECT().ListBlockDevices().Return([]lsblk.BlockDevice{ + { + Name: "mock0", + KName: device, + Type: "mocked", + Model: "mocked", + Vendor: "mocked", + State: "live", + FSType: "", + Size: "1G", + Children: nil, + Serial: "MOCK", + DevicePath: device, + }, + }, nil).Once() + // hasBindMounts in filters needs a mock + mockLSBLK.EXPECT().HasBindMounts(mock.AnythingOfType("BlockDevice")).Return(false, "", nil).Once() -func TestVGReconciler_validateLVs(t *testing.T) { - type fields struct { - executor exec.Executor - } - type args struct { - volumeGroup *lvmv1alpha1.LVMVolumeGroup + // Create VG + lvmPV := lvm.PhysicalVolume{PvName: device} + lvmVG := lvm.VolumeGroup{ + Name: vg.GetName(), + PVs: []lvm.PhysicalVolume{lvmPV}, } + mockLVM.EXPECT().CreateVG(lvmVG).Return(nil).Once() - lvsCommandForVG1 := []string{ - "lvs", - "-S", - "vgname=vg1", - "--units", - "g", - "--reportformat", - "json", - } + // Check for Thin Pool + mockLVM.EXPECT().ListLVs(vg.GetName()).Return(&lvm.LVReport{Report: make([]lvm.LVReportItem, 0)}, nil).Once() - mockExecutorForLVSOutput := func(output string) exec.Executor { - return &mockExec.MockExecutor{ - MockExecuteCommandWithOutputAsHost: func(command string, args ...string) (string, error) { - if !slices.Equal(args, lvsCommandForVG1) { - return "", fmt.Errorf("invalid args %q", args) - } - return output, nil - }, - } - } + // Create Thin Pool + mockLVM.EXPECT().CreateLV(vg.Spec.ThinPoolConfig.Name, vg.GetName(), vg.Spec.ThinPoolConfig.SizePercent).Return(nil).Once() - tests := []struct { - name string - fields fields - args args - wantErr assert.ErrorAssertionFunc - }{ - { - name: "Valid LV", - fields: fields{ - executor: mockExecutorForLVSOutput(mockLvsOutputThinPoolValid), - }, - args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ - ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, - Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ - Name: "thin-pool-1", - }}, - }}, - wantErr: assert.NoError, - }, - { - name: "Invalid LV due to Type not being Thin Pool", - fields: fields{ - executor: mockExecutorForLVSOutput(mockLvsOutputRAID), - }, - args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ - ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, - Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ - Name: "thin-pool-1", - }}, - }}, - wantErr: assert.Error, - }, - { - name: "Invalid LV due to high metadata percentage", - fields: fields{ - executor: mockExecutorForLVSOutput(mockLvsOutputThinPoolHighMetadataUse), - }, - args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ - ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, - Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ - Name: "thin-pool-1", - }}, - }}, - wantErr: assert.Error, - }, - { - name: "Invalid LV due to suspended instead of active state", - fields: fields{ - executor: mockExecutorForLVSOutput(mockLvsOutputThinPoolSuspended), - }, - args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ - ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, - Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ - Name: "thin-pool-1", - }}, - }}, - wantErr: assert.Error, - }, - { - name: "Invalid LV due to empty report", - fields: fields{ - executor: mockExecutorForLVSOutput(mockLvsOutputNoReportContent), - }, - args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ - ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, - Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ - Name: "thin-pool-1", - }}, - }}, - wantErr: assert.Error, - }, - { - name: "Invalid LV due to no LVs in report", - fields: fields{ - executor: mockExecutorForLVSOutput(mockLvsOutputNoLVsInReport), - }, - args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ - ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, - Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ - Name: "thin-pool-1", - }}, - }}, - wantErr: assert.Error, - }, - { - name: "Invalid LV due to wrong LVs in report", - fields: fields{ - executor: mockExecutorForLVSOutput(mockLvsOutputWrongLVsInReport), - }, - args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ - ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, - Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ - Name: "thin-pool-1", - }}, - }}, - wantErr: assert.Error, - }, + // Validate created Thin Pool + thinPool := lvm.LogicalVolume{ + Name: vg.Spec.ThinPoolConfig.Name, + VgName: vg.GetName(), + LvAttr: "twi-a-tz--", + LvSize: "1.0G", + MetadataPercent: "10.0", } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &VGReconciler{ - executor: tt.fields.executor, - } - tt.wantErr(t, r.validateLVs(log.IntoContext(context.Background(), testr.New(t)), tt.args.volumeGroup), fmt.Sprintf("validateLVs(%v)", tt.args.volumeGroup)) - }) + createdVG := lvm.VolumeGroup{ + Name: vg.GetName(), + VgSize: thinPool.LvSize, + PVs: []lvm.PhysicalVolume{lvmPV}, } + mockLVM.EXPECT().ListLVs(vg.GetName()).Return(&lvm.LVReport{Report: []lvm.LVReportItem{{ + Lv: []lvm.LogicalVolume{thinPool}, + }}}, nil) + // status update needs to access the vgs for node status + mockLVM.EXPECT().GetVG(vg.GetName()).Return(createdVG, nil).Once() + mockLVM.EXPECT().ListVGs().Return([]lvm.VolumeGroup{createdVG}, nil).Once() + + Expect(k8sClient.Create(ctx, vg)).To(Succeed()) + DeferCleanup(func(ctx context.Context) { + By("deleting the LVMVolumeGroup after successful verification") + mockLVM.EXPECT().LVExists(vg.Spec.ThinPoolConfig.Name, vg.GetName()).Return(true, nil).Once() + mockLVM.EXPECT().DeleteLV(vg.Spec.ThinPoolConfig.Name, vg.GetName()).Return(nil).Once() + mockLVM.EXPECT().DeleteVG(createdVG).Return(nil) + Expect(k8sClient.Delete(ctx, vg)).To(Succeed()) + Eventually(func(ctx context.Context) error { + return k8sClient.Get(ctx, client.ObjectKeyFromObject(vg), vg) + }).WithContext(ctx).Should(Satisfy(errors.IsNotFound), "no finalizers should have blocked"+ + "the LVMVolumeGroup deletion and it should not exist on the cluster anymore") + }) + + By("verifying finalizer") + Eventually(func(g Gomega, ctx context.Context) []string { + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(vg), vg)).To(Succeed()) + return vg.GetFinalizers() + }).WithContext(ctx).Should(ContainElement(fmt.Sprintf("%s/%s", NodeCleanupFinalizer, testNodeName))) + + By("verifying the Node Status contains the Volume Group in Ready State") + nodeStatus := &lvmv1alpha1.LVMVolumeGroupNodeStatus{} + nodeStatus.SetName(testNodeName) + nodeStatus.SetNamespace(testNamespaceName) + Eventually(func(g Gomega, ctx context.Context) { + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(nodeStatus), nodeStatus)).To(Succeed()) + g.Expect(nodeStatus.Spec.LVMVGStatus).ToNot(BeEmpty(), "volume group needs to be present") + g.Expect(nodeStatus.Spec.LVMVGStatus).To(ContainElement(lvmv1alpha1.VGStatus{ + Name: vg.GetName(), + Status: lvmv1alpha1.VGStatusReady, + Devices: vg.Spec.DeviceSelector.Paths, + }), "volume group needs to be ready and contain all the devices from the selector") + }).WithContext(ctx).Should(Succeed()) } diff --git a/pkg/vgmanager/vgmanager_validatelvs_test.go b/pkg/vgmanager/vgmanager_validatelvs_test.go new file mode 100644 index 000000000..d5f7b548d --- /dev/null +++ b/pkg/vgmanager/vgmanager_validatelvs_test.go @@ -0,0 +1,227 @@ +package vgmanager + +import ( + "context" + "fmt" + "testing" + + "github.com/go-logr/logr/testr" + lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" + lvmexec "github.com/openshift/lvm-operator/pkg/internal/exec" + mockExec "github.com/openshift/lvm-operator/pkg/internal/exec/test" + "github.com/openshift/lvm-operator/pkg/lvm" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/strings/slices" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +var mockLvsOutputNoReportContent = ` +{ + "report": [] +} +` + +var mockLvsOutputNoLVsInReport = ` +{ + "report": [ + { + "lv": [] + } + ] + } +` + +var mockLvsOutputWrongLVsInReport = ` +{ + "report": [ + { + "lv": [ + {"lv_name":"thin-pool-BLUB", "vg_name":"vg1", "lv_attr":"twi-a-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"10.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} + ] + } + ] + } +` + +var mockLvsOutputThinPoolValid = ` +{ + "report": [ + { + "lv": [ + {"lv_name":"thin-pool-1", "vg_name":"vg1", "lv_attr":"twi-a-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"10.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} + ] + } + ] + } +` + +var mockLvsOutputThinPoolHighMetadataUse = ` +{ + "report": [ + { + "lv": [ + {"lv_name":"thin-pool-1", "vg_name":"vg1", "lv_attr":"twi-a-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"98.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} + ] + } + ] + } +` +var mockLvsOutputThinPoolSuspended = ` +{ + "report": [ + { + "lv": [ + {"lv_name":"thin-pool-1", "vg_name":"vg1", "lv_attr":"twi-s-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"10.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} + ] + } + ] + } +` + +var mockLvsOutputRAID = ` +{ + "report": [ + { + "lv": [ + {"lv_name":"thin-pool-1", "vg_name":"vg1", "lv_attr":"rwi-a-tz--", "lv_size":"26.96g", "pool_lv":"", "origin":"", "data_percent":"0.00", "metadata_percent":"10.52", "move_pv":"", "mirror_log":"", "copy_percent":"", "convert_lv":""} + ] + } + ] + } +` + +func TestVGReconciler_validateLVs(t *testing.T) { + type fields struct { + executor lvmexec.Executor + } + type args struct { + volumeGroup *lvmv1alpha1.LVMVolumeGroup + } + + lvsCommandForVG1 := []string{ + "lvs", + "-S", + "vgname=vg1", + "--units", + "g", + "--reportformat", + "json", + } + + mockExecutorForLVSOutput := func(output string) lvmexec.Executor { + return &mockExec.MockExecutor{ + MockExecuteCommandWithOutputAsHost: func(command string, args ...string) (string, error) { + if !slices.Equal(args, lvsCommandForVG1) { + return "", fmt.Errorf("invalid args %q", args) + } + return output, nil + }, + } + } + + tests := []struct { + name string + fields fields + args args + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Valid LV", + fields: fields{ + executor: mockExecutorForLVSOutput(mockLvsOutputThinPoolValid), + }, + args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, + Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ + Name: "thin-pool-1", + }}, + }}, + wantErr: assert.NoError, + }, + { + name: "Invalid LV due to Type not being Thin Pool", + fields: fields{ + executor: mockExecutorForLVSOutput(mockLvsOutputRAID), + }, + args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, + Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ + Name: "thin-pool-1", + }}, + }}, + wantErr: assert.Error, + }, + { + name: "Invalid LV due to high metadata percentage", + fields: fields{ + executor: mockExecutorForLVSOutput(mockLvsOutputThinPoolHighMetadataUse), + }, + args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, + Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ + Name: "thin-pool-1", + }}, + }}, + wantErr: assert.Error, + }, + { + name: "Invalid LV due to suspended instead of active state", + fields: fields{ + executor: mockExecutorForLVSOutput(mockLvsOutputThinPoolSuspended), + }, + args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, + Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ + Name: "thin-pool-1", + }}, + }}, + wantErr: assert.Error, + }, + { + name: "Invalid LV due to empty report", + fields: fields{ + executor: mockExecutorForLVSOutput(mockLvsOutputNoReportContent), + }, + args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, + Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ + Name: "thin-pool-1", + }}, + }}, + wantErr: assert.Error, + }, + { + name: "Invalid LV due to no LVs in report", + fields: fields{ + executor: mockExecutorForLVSOutput(mockLvsOutputNoLVsInReport), + }, + args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, + Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ + Name: "thin-pool-1", + }}, + }}, + wantErr: assert.Error, + }, + { + name: "Invalid LV due to wrong LVs in report", + fields: fields{ + executor: mockExecutorForLVSOutput(mockLvsOutputWrongLVsInReport), + }, + args: args{volumeGroup: &lvmv1alpha1.LVMVolumeGroup{ + ObjectMeta: metav1.ObjectMeta{Name: "vg1", Namespace: "default"}, + Spec: lvmv1alpha1.LVMVolumeGroupSpec{ThinPoolConfig: &lvmv1alpha1.ThinPoolConfig{ + Name: "thin-pool-1", + }}, + }}, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &VGReconciler{LVM: lvm.NewHostLVM(tt.fields.executor)} + tt.wantErr(t, r.validateLVs(log.IntoContext(context.Background(), testr.New(t)), tt.args.volumeGroup), fmt.Sprintf("validateLVs(%v)", tt.args.volumeGroup)) + }) + } +} diff --git a/vendor/github.com/stretchr/objx/.codeclimate.yml b/vendor/github.com/stretchr/objx/.codeclimate.yml new file mode 100644 index 000000000..559fa399c --- /dev/null +++ b/vendor/github.com/stretchr/objx/.codeclimate.yml @@ -0,0 +1,21 @@ +engines: + gofmt: + enabled: true + golint: + enabled: true + govet: + enabled: true + +exclude_patterns: +- ".github/" +- "vendor/" +- "codegen/" +- "*.yml" +- ".*.yml" +- "*.md" +- "Gopkg.*" +- "doc.go" +- "type_specific_codegen_test.go" +- "type_specific_codegen.go" +- ".gitignore" +- "LICENSE" diff --git a/vendor/github.com/stretchr/objx/.gitignore b/vendor/github.com/stretchr/objx/.gitignore new file mode 100644 index 000000000..ea58090bd --- /dev/null +++ b/vendor/github.com/stretchr/objx/.gitignore @@ -0,0 +1,11 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/vendor/github.com/stretchr/objx/LICENSE b/vendor/github.com/stretchr/objx/LICENSE new file mode 100644 index 000000000..44d4d9d5a --- /dev/null +++ b/vendor/github.com/stretchr/objx/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Stretchr, Inc. +Copyright (c) 2017-2018 objx contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/stretchr/objx/README.md b/vendor/github.com/stretchr/objx/README.md new file mode 100644 index 000000000..246660b21 --- /dev/null +++ b/vendor/github.com/stretchr/objx/README.md @@ -0,0 +1,80 @@ +# Objx +[![Build Status](https://travis-ci.org/stretchr/objx.svg?branch=master)](https://travis-ci.org/stretchr/objx) +[![Go Report Card](https://goreportcard.com/badge/github.com/stretchr/objx)](https://goreportcard.com/report/github.com/stretchr/objx) +[![Maintainability](https://api.codeclimate.com/v1/badges/1d64bc6c8474c2074f2b/maintainability)](https://codeclimate.com/github/stretchr/objx/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/1d64bc6c8474c2074f2b/test_coverage)](https://codeclimate.com/github/stretchr/objx/test_coverage) +[![Sourcegraph](https://sourcegraph.com/github.com/stretchr/objx/-/badge.svg)](https://sourcegraph.com/github.com/stretchr/objx) +[![GoDoc](https://godoc.org/github.com/stretchr/objx?status.svg)](https://godoc.org/github.com/stretchr/objx) + +Objx - Go package for dealing with maps, slices, JSON and other data. + +Get started: + +- Install Objx with [one line of code](#installation), or [update it with another](#staying-up-to-date) +- Check out the API Documentation http://godoc.org/github.com/stretchr/objx + +## Overview +Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes a powerful `Get` method (among others) that allows you to easily and quickly get access to data within the map, without having to worry too much about type assertions, missing data, default values etc. + +### Pattern +Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. Call one of the `objx.` functions to create your `objx.Map` to get going: + + m, err := objx.FromJSON(json) + +NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, the rest will be optimistic and try to figure things out without panicking. + +Use `Get` to access the value you're interested in. You can use dot and array +notation too: + + m.Get("places[0].latlng") + +Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type. + + if m.Get("code").IsStr() { // Your code... } + +Or you can just assume the type, and use one of the strong type methods to extract the real value: + + m.Get("code").Int() + +If there's no value there (or if it's the wrong type) then a default value will be returned, or you can be explicit about the default value. + + Get("code").Int(-1) + +If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, manipulating and selecting that data. You can find out more by exploring the index below. + +### Reading data +A simple example of how to use Objx: + + // Use MustFromJSON to make an objx.Map from some JSON + m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) + + // Get the details + name := m.Get("name").Str() + age := m.Get("age").Int() + + // Get their nickname (or use their name if they don't have one) + nickname := m.Get("nickname").Str(name) + +### Ranging +Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For example, to `range` the data, do what you would expect: + + m := objx.MustFromJSON(json) + for key, value := range m { + // Your code... + } + +## Installation +To install Objx, use go get: + + go get github.com/stretchr/objx + +### Staying up to date +To update Objx to the latest version, run: + + go get -u github.com/stretchr/objx + +### Supported go versions +We support the lastest three major Go versions, which are 1.10, 1.11 and 1.12 at the moment. + +## Contributing +Please feel free to submit issues, fork the repository and send pull requests! diff --git a/vendor/github.com/stretchr/objx/Taskfile.yml b/vendor/github.com/stretchr/objx/Taskfile.yml new file mode 100644 index 000000000..7746f516d --- /dev/null +++ b/vendor/github.com/stretchr/objx/Taskfile.yml @@ -0,0 +1,30 @@ +version: '2' + +env: + GOFLAGS: -mod=vendor + +tasks: + default: + deps: [test] + + lint: + desc: Checks code style + cmds: + - gofmt -d -s *.go + - go vet ./... + silent: true + + lint-fix: + desc: Fixes code style + cmds: + - gofmt -w -s *.go + + test: + desc: Runs go tests + cmds: + - go test -race ./... + + test-coverage: + desc: Runs go tests and calculates test coverage + cmds: + - go test -race -coverprofile=c.out ./... diff --git a/vendor/github.com/stretchr/objx/accessors.go b/vendor/github.com/stretchr/objx/accessors.go new file mode 100644 index 000000000..4c6045588 --- /dev/null +++ b/vendor/github.com/stretchr/objx/accessors.go @@ -0,0 +1,197 @@ +package objx + +import ( + "reflect" + "regexp" + "strconv" + "strings" +) + +const ( + // PathSeparator is the character used to separate the elements + // of the keypath. + // + // For example, `location.address.city` + PathSeparator string = "." + + // arrayAccesRegexString is the regex used to extract the array number + // from the access path + arrayAccesRegexString = `^(.+)\[([0-9]+)\]$` + + // mapAccessRegexString is the regex used to extract the map key + // from the access path + mapAccessRegexString = `^([^\[]*)\[([^\]]+)\](.*)$` +) + +// arrayAccesRegex is the compiled arrayAccesRegexString +var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString) + +// mapAccessRegex is the compiled mapAccessRegexString +var mapAccessRegex = regexp.MustCompile(mapAccessRegexString) + +// Get gets the value using the specified selector and +// returns it inside a new Obj object. +// +// If it cannot find the value, Get will return a nil +// value inside an instance of Obj. +// +// Get can only operate directly on map[string]interface{} and []interface. +// +// Example +// +// To access the title of the third chapter of the second book, do: +// +// o.Get("books[1].chapters[2].title") +func (m Map) Get(selector string) *Value { + rawObj := access(m, selector, nil, false) + return &Value{data: rawObj} +} + +// Set sets the value using the specified selector and +// returns the object on which Set was called. +// +// Set can only operate directly on map[string]interface{} and []interface +// +// Example +// +// To set the title of the third chapter of the second book, do: +// +// o.Set("books[1].chapters[2].title","Time to Go") +func (m Map) Set(selector string, value interface{}) Map { + access(m, selector, value, true) + return m +} + +// getIndex returns the index, which is hold in s by two braches. +// It also returns s withour the index part, e.g. name[1] will return (1, name). +// If no index is found, -1 is returned +func getIndex(s string) (int, string) { + arrayMatches := arrayAccesRegex.FindStringSubmatch(s) + if len(arrayMatches) > 0 { + // Get the key into the map + selector := arrayMatches[1] + // Get the index into the array at the key + // We know this cannt fail because arrayMatches[2] is an int for sure + index, _ := strconv.Atoi(arrayMatches[2]) + return index, selector + } + return -1, s +} + +// getKey returns the key which is held in s by two brackets. +// It also returns the next selector. +func getKey(s string) (string, string) { + selSegs := strings.SplitN(s, PathSeparator, 2) + thisSel := selSegs[0] + nextSel := "" + + if len(selSegs) > 1 { + nextSel = selSegs[1] + } + + mapMatches := mapAccessRegex.FindStringSubmatch(s) + if len(mapMatches) > 0 { + if _, err := strconv.Atoi(mapMatches[2]); err != nil { + thisSel = mapMatches[1] + nextSel = "[" + mapMatches[2] + "]" + mapMatches[3] + + if thisSel == "" { + thisSel = mapMatches[2] + nextSel = mapMatches[3] + } + + if nextSel == "" { + selSegs = []string{"", ""} + } else if nextSel[0] == '.' { + nextSel = nextSel[1:] + } + } + } + + return thisSel, nextSel +} + +// access accesses the object using the selector and performs the +// appropriate action. +func access(current interface{}, selector string, value interface{}, isSet bool) interface{} { + thisSel, nextSel := getKey(selector) + + indexes := []int{} + for strings.Contains(thisSel, "[") { + prevSel := thisSel + index := -1 + index, thisSel = getIndex(thisSel) + indexes = append(indexes, index) + if prevSel == thisSel { + break + } + } + + if curMap, ok := current.(Map); ok { + current = map[string]interface{}(curMap) + } + // get the object in question + switch current.(type) { + case map[string]interface{}: + curMSI := current.(map[string]interface{}) + if nextSel == "" && isSet { + curMSI[thisSel] = value + return nil + } + + _, ok := curMSI[thisSel].(map[string]interface{}) + if !ok { + _, ok = curMSI[thisSel].(Map) + } + + if (curMSI[thisSel] == nil || !ok) && len(indexes) == 0 && isSet { + curMSI[thisSel] = map[string]interface{}{} + } + + current = curMSI[thisSel] + default: + current = nil + } + + // do we need to access the item of an array? + if len(indexes) > 0 { + num := len(indexes) + for num > 0 { + num-- + index := indexes[num] + indexes = indexes[:num] + if array, ok := interSlice(current); ok { + if index < len(array) { + current = array[index] + } else { + current = nil + break + } + } + } + } + + if nextSel != "" { + current = access(current, nextSel, value, isSet) + } + return current +} + +func interSlice(slice interface{}) ([]interface{}, bool) { + if array, ok := slice.([]interface{}); ok { + return array, ok + } + + s := reflect.ValueOf(slice) + if s.Kind() != reflect.Slice { + return nil, false + } + + ret := make([]interface{}, s.Len()) + + for i := 0; i < s.Len(); i++ { + ret[i] = s.Index(i).Interface() + } + + return ret, true +} diff --git a/vendor/github.com/stretchr/objx/conversions.go b/vendor/github.com/stretchr/objx/conversions.go new file mode 100644 index 000000000..080aa46e4 --- /dev/null +++ b/vendor/github.com/stretchr/objx/conversions.go @@ -0,0 +1,280 @@ +package objx + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/url" + "strconv" +) + +// SignatureSeparator is the character that is used to +// separate the Base64 string from the security signature. +const SignatureSeparator = "_" + +// URLValuesSliceKeySuffix is the character that is used to +// specify a suffic for slices parsed by URLValues. +// If the suffix is set to "[i]", then the index of the slice +// is used in place of i +// Ex: Suffix "[]" would have the form a[]=b&a[]=c +// OR Suffix "[i]" would have the form a[0]=b&a[1]=c +// OR Suffix "" would have the form a=b&a=c +var urlValuesSliceKeySuffix = "[]" + +const ( + URLValuesSliceKeySuffixEmpty = "" + URLValuesSliceKeySuffixArray = "[]" + URLValuesSliceKeySuffixIndex = "[i]" +) + +// SetURLValuesSliceKeySuffix sets the character that is used to +// specify a suffic for slices parsed by URLValues. +// If the suffix is set to "[i]", then the index of the slice +// is used in place of i +// Ex: Suffix "[]" would have the form a[]=b&a[]=c +// OR Suffix "[i]" would have the form a[0]=b&a[1]=c +// OR Suffix "" would have the form a=b&a=c +func SetURLValuesSliceKeySuffix(s string) error { + if s == URLValuesSliceKeySuffixEmpty || s == URLValuesSliceKeySuffixArray || s == URLValuesSliceKeySuffixIndex { + urlValuesSliceKeySuffix = s + return nil + } + + return errors.New("objx: Invalid URLValuesSliceKeySuffix provided.") +} + +// JSON converts the contained object to a JSON string +// representation +func (m Map) JSON() (string, error) { + for k, v := range m { + m[k] = cleanUp(v) + } + + result, err := json.Marshal(m) + if err != nil { + err = errors.New("objx: JSON encode failed with: " + err.Error()) + } + return string(result), err +} + +func cleanUpInterfaceArray(in []interface{}) []interface{} { + result := make([]interface{}, len(in)) + for i, v := range in { + result[i] = cleanUp(v) + } + return result +} + +func cleanUpInterfaceMap(in map[interface{}]interface{}) Map { + result := Map{} + for k, v := range in { + result[fmt.Sprintf("%v", k)] = cleanUp(v) + } + return result +} + +func cleanUpStringMap(in map[string]interface{}) Map { + result := Map{} + for k, v := range in { + result[k] = cleanUp(v) + } + return result +} + +func cleanUpMSIArray(in []map[string]interface{}) []Map { + result := make([]Map, len(in)) + for i, v := range in { + result[i] = cleanUpStringMap(v) + } + return result +} + +func cleanUpMapArray(in []Map) []Map { + result := make([]Map, len(in)) + for i, v := range in { + result[i] = cleanUpStringMap(v) + } + return result +} + +func cleanUp(v interface{}) interface{} { + switch v := v.(type) { + case []interface{}: + return cleanUpInterfaceArray(v) + case []map[string]interface{}: + return cleanUpMSIArray(v) + case map[interface{}]interface{}: + return cleanUpInterfaceMap(v) + case Map: + return cleanUpStringMap(v) + case []Map: + return cleanUpMapArray(v) + default: + return v + } +} + +// MustJSON converts the contained object to a JSON string +// representation and panics if there is an error +func (m Map) MustJSON() string { + result, err := m.JSON() + if err != nil { + panic(err.Error()) + } + return result +} + +// Base64 converts the contained object to a Base64 string +// representation of the JSON string representation +func (m Map) Base64() (string, error) { + var buf bytes.Buffer + + jsonData, err := m.JSON() + if err != nil { + return "", err + } + + encoder := base64.NewEncoder(base64.StdEncoding, &buf) + _, _ = encoder.Write([]byte(jsonData)) + _ = encoder.Close() + + return buf.String(), nil +} + +// MustBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and panics +// if there is an error +func (m Map) MustBase64() string { + result, err := m.Base64() + if err != nil { + panic(err.Error()) + } + return result +} + +// SignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key. +func (m Map) SignedBase64(key string) (string, error) { + base64, err := m.Base64() + if err != nil { + return "", err + } + + sig := HashWithKey(base64, key) + return base64 + SignatureSeparator + sig, nil +} + +// MustSignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key and panics if there is an error +func (m Map) MustSignedBase64(key string) string { + result, err := m.SignedBase64(key) + if err != nil { + panic(err.Error()) + } + return result +} + +/* + URL Query + ------------------------------------------------ +*/ + +// URLValues creates a url.Values object from an Obj. This +// function requires that the wrapped object be a map[string]interface{} +func (m Map) URLValues() url.Values { + vals := make(url.Values) + + m.parseURLValues(m, vals, "") + + return vals +} + +func (m Map) parseURLValues(queryMap Map, vals url.Values, key string) { + useSliceIndex := false + if urlValuesSliceKeySuffix == "[i]" { + useSliceIndex = true + } + + for k, v := range queryMap { + val := &Value{data: v} + switch { + case val.IsObjxMap(): + if key == "" { + m.parseURLValues(val.ObjxMap(), vals, k) + } else { + m.parseURLValues(val.ObjxMap(), vals, key+"["+k+"]") + } + case val.IsObjxMapSlice(): + sliceKey := k + if key != "" { + sliceKey = key + "[" + k + "]" + } + + if useSliceIndex { + for i, sv := range val.MustObjxMapSlice() { + sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]" + m.parseURLValues(sv, vals, sk) + } + } else { + sliceKey = sliceKey + urlValuesSliceKeySuffix + for _, sv := range val.MustObjxMapSlice() { + m.parseURLValues(sv, vals, sliceKey) + } + } + case val.IsMSISlice(): + sliceKey := k + if key != "" { + sliceKey = key + "[" + k + "]" + } + + if useSliceIndex { + for i, sv := range val.MustMSISlice() { + sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]" + m.parseURLValues(New(sv), vals, sk) + } + } else { + sliceKey = sliceKey + urlValuesSliceKeySuffix + for _, sv := range val.MustMSISlice() { + m.parseURLValues(New(sv), vals, sliceKey) + } + } + case val.IsStrSlice(), val.IsBoolSlice(), + val.IsFloat32Slice(), val.IsFloat64Slice(), + val.IsIntSlice(), val.IsInt8Slice(), val.IsInt16Slice(), val.IsInt32Slice(), val.IsInt64Slice(), + val.IsUintSlice(), val.IsUint8Slice(), val.IsUint16Slice(), val.IsUint32Slice(), val.IsUint64Slice(): + + sliceKey := k + if key != "" { + sliceKey = key + "[" + k + "]" + } + + if useSliceIndex { + for i, sv := range val.StringSlice() { + sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]" + vals.Set(sk, sv) + } + } else { + sliceKey = sliceKey + urlValuesSliceKeySuffix + vals[sliceKey] = val.StringSlice() + } + + default: + if key == "" { + vals.Set(k, val.String()) + } else { + vals.Set(key+"["+k+"]", val.String()) + } + } + } +} + +// URLQuery gets an encoded URL query representing the given +// Obj. This function requires that the wrapped object be a +// map[string]interface{} +func (m Map) URLQuery() (string, error) { + return m.URLValues().Encode(), nil +} diff --git a/vendor/github.com/stretchr/objx/doc.go b/vendor/github.com/stretchr/objx/doc.go new file mode 100644 index 000000000..6d6af1a83 --- /dev/null +++ b/vendor/github.com/stretchr/objx/doc.go @@ -0,0 +1,66 @@ +/* +Objx - Go package for dealing with maps, slices, JSON and other data. + +Overview + +Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes +a powerful `Get` method (among others) that allows you to easily and quickly get +access to data within the map, without having to worry too much about type assertions, +missing data, default values etc. + +Pattern + +Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. +Call one of the `objx.` functions to create your `objx.Map` to get going: + + m, err := objx.FromJSON(json) + +NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, +the rest will be optimistic and try to figure things out without panicking. + +Use `Get` to access the value you're interested in. You can use dot and array +notation too: + + m.Get("places[0].latlng") + +Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type. + + if m.Get("code").IsStr() { // Your code... } + +Or you can just assume the type, and use one of the strong type methods to extract the real value: + + m.Get("code").Int() + +If there's no value there (or if it's the wrong type) then a default value will be returned, +or you can be explicit about the default value. + + Get("code").Int(-1) + +If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, +manipulating and selecting that data. You can find out more by exploring the index below. + +Reading data + +A simple example of how to use Objx: + + // Use MustFromJSON to make an objx.Map from some JSON + m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) + + // Get the details + name := m.Get("name").Str() + age := m.Get("age").Int() + + // Get their nickname (or use their name if they don't have one) + nickname := m.Get("nickname").Str(name) + +Ranging + +Since `objx.Map` is a `map[string]interface{}` you can treat it as such. +For example, to `range` the data, do what you would expect: + + m := objx.MustFromJSON(json) + for key, value := range m { + // Your code... + } +*/ +package objx diff --git a/vendor/github.com/stretchr/objx/map.go b/vendor/github.com/stretchr/objx/map.go new file mode 100644 index 000000000..a64712a08 --- /dev/null +++ b/vendor/github.com/stretchr/objx/map.go @@ -0,0 +1,215 @@ +package objx + +import ( + "encoding/base64" + "encoding/json" + "errors" + "io/ioutil" + "net/url" + "strings" +) + +// MSIConvertable is an interface that defines methods for converting your +// custom types to a map[string]interface{} representation. +type MSIConvertable interface { + // MSI gets a map[string]interface{} (msi) representing the + // object. + MSI() map[string]interface{} +} + +// Map provides extended functionality for working with +// untyped data, in particular map[string]interface (msi). +type Map map[string]interface{} + +// Value returns the internal value instance +func (m Map) Value() *Value { + return &Value{data: m} +} + +// Nil represents a nil Map. +var Nil = New(nil) + +// New creates a new Map containing the map[string]interface{} in the data argument. +// If the data argument is not a map[string]interface, New attempts to call the +// MSI() method on the MSIConvertable interface to create one. +func New(data interface{}) Map { + if _, ok := data.(map[string]interface{}); !ok { + if converter, ok := data.(MSIConvertable); ok { + data = converter.MSI() + } else { + return nil + } + } + return Map(data.(map[string]interface{})) +} + +// MSI creates a map[string]interface{} and puts it inside a new Map. +// +// The arguments follow a key, value pattern. +// +// +// Returns nil if any key argument is non-string or if there are an odd number of arguments. +// +// Example +// +// To easily create Maps: +// +// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true)) +// +// // creates an Map equivalent to +// m := objx.Map{"name": "Mat", "age": 29, "subobj": objx.Map{"active": true}} +func MSI(keyAndValuePairs ...interface{}) Map { + newMap := Map{} + keyAndValuePairsLen := len(keyAndValuePairs) + if keyAndValuePairsLen%2 != 0 { + return nil + } + for i := 0; i < keyAndValuePairsLen; i = i + 2 { + key := keyAndValuePairs[i] + value := keyAndValuePairs[i+1] + + // make sure the key is a string + keyString, keyStringOK := key.(string) + if !keyStringOK { + return nil + } + newMap[keyString] = value + } + return newMap +} + +// ****** Conversion Constructors + +// MustFromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Panics if the JSON is invalid. +func MustFromJSON(jsonString string) Map { + o, err := FromJSON(jsonString) + if err != nil { + panic("objx: MustFromJSON failed with error: " + err.Error()) + } + return o +} + +// MustFromJSONSlice creates a new slice of Map containing the data specified in the +// jsonString. Works with jsons with a top level array +// +// Panics if the JSON is invalid. +func MustFromJSONSlice(jsonString string) []Map { + slice, err := FromJSONSlice(jsonString) + if err != nil { + panic("objx: MustFromJSONSlice failed with error: " + err.Error()) + } + return slice +} + +// FromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Returns an error if the JSON is invalid. +func FromJSON(jsonString string) (Map, error) { + var m Map + err := json.Unmarshal([]byte(jsonString), &m) + if err != nil { + return Nil, err + } + return m, nil +} + +// FromJSONSlice creates a new slice of Map containing the data specified in the +// jsonString. Works with jsons with a top level array +// +// Returns an error if the JSON is invalid. +func FromJSONSlice(jsonString string) ([]Map, error) { + var slice []Map + err := json.Unmarshal([]byte(jsonString), &slice) + if err != nil { + return nil, err + } + return slice, nil +} + +// FromBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by Base64 +func FromBase64(base64String string) (Map, error) { + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String)) + decoded, err := ioutil.ReadAll(decoder) + if err != nil { + return nil, err + } + return FromJSON(string(decoded)) +} + +// MustFromBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromBase64(base64String string) Map { + result, err := FromBase64(base64String) + if err != nil { + panic("objx: MustFromBase64 failed with error: " + err.Error()) + } + return result +} + +// FromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by SignedBase64 +func FromSignedBase64(base64String, key string) (Map, error) { + parts := strings.Split(base64String, SignatureSeparator) + if len(parts) != 2 { + return nil, errors.New("objx: Signed base64 string is malformed") + } + + sig := HashWithKey(parts[0], key) + if parts[1] != sig { + return nil, errors.New("objx: Signature for base64 data does not match") + } + return FromBase64(parts[0]) +} + +// MustFromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromSignedBase64(base64String, key string) Map { + result, err := FromSignedBase64(base64String, key) + if err != nil { + panic("objx: MustFromSignedBase64 failed with error: " + err.Error()) + } + return result +} + +// FromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +func FromURLQuery(query string) (Map, error) { + vals, err := url.ParseQuery(query) + if err != nil { + return nil, err + } + m := Map{} + for k, vals := range vals { + m[k] = vals[0] + } + return m, nil +} + +// MustFromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +// +// Panics if it encounters an error +func MustFromURLQuery(query string) Map { + o, err := FromURLQuery(query) + if err != nil { + panic("objx: MustFromURLQuery failed with error: " + err.Error()) + } + return o +} diff --git a/vendor/github.com/stretchr/objx/mutations.go b/vendor/github.com/stretchr/objx/mutations.go new file mode 100644 index 000000000..c3400a3f7 --- /dev/null +++ b/vendor/github.com/stretchr/objx/mutations.go @@ -0,0 +1,77 @@ +package objx + +// Exclude returns a new Map with the keys in the specified []string +// excluded. +func (m Map) Exclude(exclude []string) Map { + excluded := make(Map) + for k, v := range m { + if !contains(exclude, k) { + excluded[k] = v + } + } + return excluded +} + +// Copy creates a shallow copy of the Obj. +func (m Map) Copy() Map { + copied := Map{} + for k, v := range m { + copied[k] = v + } + return copied +} + +// Merge blends the specified map with a copy of this map and returns the result. +// +// Keys that appear in both will be selected from the specified map. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) Merge(merge Map) Map { + return m.Copy().MergeHere(merge) +} + +// MergeHere blends the specified map with this map and returns the current map. +// +// Keys that appear in both will be selected from the specified map. The original map +// will be modified. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) MergeHere(merge Map) Map { + for k, v := range merge { + m[k] = v + } + return m +} + +// Transform builds a new Obj giving the transformer a chance +// to change the keys and values as it goes. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map { + newMap := Map{} + for k, v := range m { + modifiedKey, modifiedVal := transformer(k, v) + newMap[modifiedKey] = modifiedVal + } + return newMap +} + +// TransformKeys builds a new map using the specified key mapping. +// +// Unspecified keys will be unaltered. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) TransformKeys(mapping map[string]string) Map { + return m.Transform(func(key string, value interface{}) (string, interface{}) { + if newKey, ok := mapping[key]; ok { + return newKey, value + } + return key, value + }) +} + +// Checks if a string slice contains a string +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/vendor/github.com/stretchr/objx/security.go b/vendor/github.com/stretchr/objx/security.go new file mode 100644 index 000000000..692be8e2a --- /dev/null +++ b/vendor/github.com/stretchr/objx/security.go @@ -0,0 +1,12 @@ +package objx + +import ( + "crypto/sha1" + "encoding/hex" +) + +// HashWithKey hashes the specified string using the security key +func HashWithKey(data, key string) string { + d := sha1.Sum([]byte(data + ":" + key)) + return hex.EncodeToString(d[:]) +} diff --git a/vendor/github.com/stretchr/objx/tests.go b/vendor/github.com/stretchr/objx/tests.go new file mode 100644 index 000000000..d9e0b479a --- /dev/null +++ b/vendor/github.com/stretchr/objx/tests.go @@ -0,0 +1,17 @@ +package objx + +// Has gets whether there is something at the specified selector +// or not. +// +// If m is nil, Has will always return false. +func (m Map) Has(selector string) bool { + if m == nil { + return false + } + return !m.Get(selector).IsNil() +} + +// IsNil gets whether the data is nil or not. +func (v *Value) IsNil() bool { + return v == nil || v.data == nil +} diff --git a/vendor/github.com/stretchr/objx/type_specific.go b/vendor/github.com/stretchr/objx/type_specific.go new file mode 100644 index 000000000..80f88d9fa --- /dev/null +++ b/vendor/github.com/stretchr/objx/type_specific.go @@ -0,0 +1,346 @@ +package objx + +/* + MSI (map[string]interface{} and []map[string]interface{}) +*/ + +// MSI gets the value as a map[string]interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) MSI(optionalDefault ...map[string]interface{}) map[string]interface{} { + if s, ok := v.data.(map[string]interface{}); ok { + return s + } + if s, ok := v.data.(Map); ok { + return map[string]interface{}(s) + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSI gets the value as a map[string]interface{}. +// +// Panics if the object is not a map[string]interface{}. +func (v *Value) MustMSI() map[string]interface{} { + if s, ok := v.data.(Map); ok { + return map[string]interface{}(s) + } + return v.data.(map[string]interface{}) +} + +// MSISlice gets the value as a []map[string]interface{}, returns the optionalDefault +// value or nil if the value is not a []map[string]interface{}. +func (v *Value) MSISlice(optionalDefault ...[]map[string]interface{}) []map[string]interface{} { + if s, ok := v.data.([]map[string]interface{}); ok { + return s + } + + s := v.ObjxMapSlice() + if s == nil { + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil + } + + result := make([]map[string]interface{}, len(s)) + for i := range s { + result[i] = s[i].Value().MSI() + } + return result +} + +// MustMSISlice gets the value as a []map[string]interface{}. +// +// Panics if the object is not a []map[string]interface{}. +func (v *Value) MustMSISlice() []map[string]interface{} { + if s := v.MSISlice(); s != nil { + return s + } + + return v.data.([]map[string]interface{}) +} + +// IsMSI gets whether the object contained is a map[string]interface{} or not. +func (v *Value) IsMSI() bool { + _, ok := v.data.(map[string]interface{}) + if !ok { + _, ok = v.data.(Map) + } + return ok +} + +// IsMSISlice gets whether the object contained is a []map[string]interface{} or not. +func (v *Value) IsMSISlice() bool { + _, ok := v.data.([]map[string]interface{}) + if !ok { + _, ok = v.data.([]Map) + if !ok { + s, ok := v.data.([]interface{}) + if ok { + for i := range s { + switch s[i].(type) { + case Map: + case map[string]interface{}: + default: + return false + } + } + return true + } + } + } + return ok +} + +// EachMSI calls the specified callback for each object +// in the []map[string]interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachMSI(callback func(int, map[string]interface{}) bool) *Value { + for index, val := range v.MustMSISlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereMSI uses the specified decider function to select items +// from the []map[string]interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereMSI(decider func(int, map[string]interface{}) bool) *Value { + var selected []map[string]interface{} + v.EachMSI(func(index int, val map[string]interface{}) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupMSI uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]map[string]interface{}. +func (v *Value) GroupMSI(grouper func(int, map[string]interface{}) string) *Value { + groups := make(map[string][]map[string]interface{}) + v.EachMSI(func(index int, val map[string]interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]map[string]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceMSI uses the specified function to replace each map[string]interface{}s +// by iterating each item. The data in the returned result will be a +// []map[string]interface{} containing the replaced items. +func (v *Value) ReplaceMSI(replacer func(int, map[string]interface{}) map[string]interface{}) *Value { + arr := v.MustMSISlice() + replaced := make([]map[string]interface{}, len(arr)) + v.EachMSI(func(index int, val map[string]interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectMSI uses the specified collector function to collect a value +// for each of the map[string]interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectMSI(collector func(int, map[string]interface{}) interface{}) *Value { + arr := v.MustMSISlice() + collected := make([]interface{}, len(arr)) + v.EachMSI(func(index int, val map[string]interface{}) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + ObjxMap ((Map) and [](Map)) +*/ + +// ObjxMap gets the value as a (Map), returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) ObjxMap(optionalDefault ...(Map)) Map { + if s, ok := v.data.((Map)); ok { + return s + } + if s, ok := v.data.(map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return New(nil) +} + +// MustObjxMap gets the value as a (Map). +// +// Panics if the object is not a (Map). +func (v *Value) MustObjxMap() Map { + if s, ok := v.data.(map[string]interface{}); ok { + return s + } + return v.data.((Map)) +} + +// ObjxMapSlice gets the value as a [](Map), returns the optionalDefault +// value or nil if the value is not a [](Map). +func (v *Value) ObjxMapSlice(optionalDefault ...[](Map)) [](Map) { + if s, ok := v.data.([]Map); ok { + return s + } + + if s, ok := v.data.([]map[string]interface{}); ok { + result := make([]Map, len(s)) + for i := range s { + result[i] = s[i] + } + return result + } + + s, ok := v.data.([]interface{}) + if !ok { + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil + } + + result := make([]Map, len(s)) + for i := range s { + switch s[i].(type) { + case Map: + result[i] = s[i].(Map) + case map[string]interface{}: + result[i] = New(s[i]) + default: + return nil + } + } + return result +} + +// MustObjxMapSlice gets the value as a [](Map). +// +// Panics if the object is not a [](Map). +func (v *Value) MustObjxMapSlice() [](Map) { + if s := v.ObjxMapSlice(); s != nil { + return s + } + return v.data.([](Map)) +} + +// IsObjxMap gets whether the object contained is a (Map) or not. +func (v *Value) IsObjxMap() bool { + _, ok := v.data.((Map)) + if !ok { + _, ok = v.data.(map[string]interface{}) + } + return ok +} + +// IsObjxMapSlice gets whether the object contained is a [](Map) or not. +func (v *Value) IsObjxMapSlice() bool { + _, ok := v.data.([](Map)) + if !ok { + _, ok = v.data.([]map[string]interface{}) + if !ok { + s, ok := v.data.([]interface{}) + if ok { + for i := range s { + switch s[i].(type) { + case Map: + case map[string]interface{}: + default: + return false + } + } + return true + } + } + } + + return ok +} + +// EachObjxMap calls the specified callback for each object +// in the [](Map). +// +// Panics if the object is the wrong type. +func (v *Value) EachObjxMap(callback func(int, Map) bool) *Value { + for index, val := range v.MustObjxMapSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereObjxMap uses the specified decider function to select items +// from the [](Map). The object contained in the result will contain +// only the selected items. +func (v *Value) WhereObjxMap(decider func(int, Map) bool) *Value { + var selected [](Map) + v.EachObjxMap(func(index int, val Map) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupObjxMap uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][](Map). +func (v *Value) GroupObjxMap(grouper func(int, Map) string) *Value { + groups := make(map[string][](Map)) + v.EachObjxMap(func(index int, val Map) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([](Map), 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceObjxMap uses the specified function to replace each (Map)s +// by iterating each item. The data in the returned result will be a +// [](Map) containing the replaced items. +func (v *Value) ReplaceObjxMap(replacer func(int, Map) Map) *Value { + arr := v.MustObjxMapSlice() + replaced := make([](Map), len(arr)) + v.EachObjxMap(func(index int, val Map) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectObjxMap uses the specified collector function to collect a value +// for each of the (Map)s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectObjxMap(collector func(int, Map) interface{}) *Value { + arr := v.MustObjxMapSlice() + collected := make([]interface{}, len(arr)) + v.EachObjxMap(func(index int, val Map) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} diff --git a/vendor/github.com/stretchr/objx/type_specific_codegen.go b/vendor/github.com/stretchr/objx/type_specific_codegen.go new file mode 100644 index 000000000..45850456e --- /dev/null +++ b/vendor/github.com/stretchr/objx/type_specific_codegen.go @@ -0,0 +1,2261 @@ +package objx + +/* + Inter (interface{} and []interface{}) +*/ + +// Inter gets the value as a interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Inter(optionalDefault ...interface{}) interface{} { + if s, ok := v.data.(interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInter gets the value as a interface{}. +// +// Panics if the object is not a interface{}. +func (v *Value) MustInter() interface{} { + return v.data.(interface{}) +} + +// InterSlice gets the value as a []interface{}, returns the optionalDefault +// value or nil if the value is not a []interface{}. +func (v *Value) InterSlice(optionalDefault ...[]interface{}) []interface{} { + if s, ok := v.data.([]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInterSlice gets the value as a []interface{}. +// +// Panics if the object is not a []interface{}. +func (v *Value) MustInterSlice() []interface{} { + return v.data.([]interface{}) +} + +// IsInter gets whether the object contained is a interface{} or not. +func (v *Value) IsInter() bool { + _, ok := v.data.(interface{}) + return ok +} + +// IsInterSlice gets whether the object contained is a []interface{} or not. +func (v *Value) IsInterSlice() bool { + _, ok := v.data.([]interface{}) + return ok +} + +// EachInter calls the specified callback for each object +// in the []interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachInter(callback func(int, interface{}) bool) *Value { + for index, val := range v.MustInterSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInter uses the specified decider function to select items +// from the []interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInter(decider func(int, interface{}) bool) *Value { + var selected []interface{} + v.EachInter(func(index int, val interface{}) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInter uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]interface{}. +func (v *Value) GroupInter(grouper func(int, interface{}) string) *Value { + groups := make(map[string][]interface{}) + v.EachInter(func(index int, val interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInter uses the specified function to replace each interface{}s +// by iterating each item. The data in the returned result will be a +// []interface{} containing the replaced items. +func (v *Value) ReplaceInter(replacer func(int, interface{}) interface{}) *Value { + arr := v.MustInterSlice() + replaced := make([]interface{}, len(arr)) + v.EachInter(func(index int, val interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInter uses the specified collector function to collect a value +// for each of the interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInter(collector func(int, interface{}) interface{}) *Value { + arr := v.MustInterSlice() + collected := make([]interface{}, len(arr)) + v.EachInter(func(index int, val interface{}) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Bool (bool and []bool) +*/ + +// Bool gets the value as a bool, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Bool(optionalDefault ...bool) bool { + if s, ok := v.data.(bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return false +} + +// MustBool gets the value as a bool. +// +// Panics if the object is not a bool. +func (v *Value) MustBool() bool { + return v.data.(bool) +} + +// BoolSlice gets the value as a []bool, returns the optionalDefault +// value or nil if the value is not a []bool. +func (v *Value) BoolSlice(optionalDefault ...[]bool) []bool { + if s, ok := v.data.([]bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustBoolSlice gets the value as a []bool. +// +// Panics if the object is not a []bool. +func (v *Value) MustBoolSlice() []bool { + return v.data.([]bool) +} + +// IsBool gets whether the object contained is a bool or not. +func (v *Value) IsBool() bool { + _, ok := v.data.(bool) + return ok +} + +// IsBoolSlice gets whether the object contained is a []bool or not. +func (v *Value) IsBoolSlice() bool { + _, ok := v.data.([]bool) + return ok +} + +// EachBool calls the specified callback for each object +// in the []bool. +// +// Panics if the object is the wrong type. +func (v *Value) EachBool(callback func(int, bool) bool) *Value { + for index, val := range v.MustBoolSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereBool uses the specified decider function to select items +// from the []bool. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereBool(decider func(int, bool) bool) *Value { + var selected []bool + v.EachBool(func(index int, val bool) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupBool uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]bool. +func (v *Value) GroupBool(grouper func(int, bool) string) *Value { + groups := make(map[string][]bool) + v.EachBool(func(index int, val bool) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]bool, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceBool uses the specified function to replace each bools +// by iterating each item. The data in the returned result will be a +// []bool containing the replaced items. +func (v *Value) ReplaceBool(replacer func(int, bool) bool) *Value { + arr := v.MustBoolSlice() + replaced := make([]bool, len(arr)) + v.EachBool(func(index int, val bool) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectBool uses the specified collector function to collect a value +// for each of the bools in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectBool(collector func(int, bool) interface{}) *Value { + arr := v.MustBoolSlice() + collected := make([]interface{}, len(arr)) + v.EachBool(func(index int, val bool) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Str (string and []string) +*/ + +// Str gets the value as a string, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Str(optionalDefault ...string) string { + if s, ok := v.data.(string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return "" +} + +// MustStr gets the value as a string. +// +// Panics if the object is not a string. +func (v *Value) MustStr() string { + return v.data.(string) +} + +// StrSlice gets the value as a []string, returns the optionalDefault +// value or nil if the value is not a []string. +func (v *Value) StrSlice(optionalDefault ...[]string) []string { + if s, ok := v.data.([]string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustStrSlice gets the value as a []string. +// +// Panics if the object is not a []string. +func (v *Value) MustStrSlice() []string { + return v.data.([]string) +} + +// IsStr gets whether the object contained is a string or not. +func (v *Value) IsStr() bool { + _, ok := v.data.(string) + return ok +} + +// IsStrSlice gets whether the object contained is a []string or not. +func (v *Value) IsStrSlice() bool { + _, ok := v.data.([]string) + return ok +} + +// EachStr calls the specified callback for each object +// in the []string. +// +// Panics if the object is the wrong type. +func (v *Value) EachStr(callback func(int, string) bool) *Value { + for index, val := range v.MustStrSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereStr uses the specified decider function to select items +// from the []string. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereStr(decider func(int, string) bool) *Value { + var selected []string + v.EachStr(func(index int, val string) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupStr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]string. +func (v *Value) GroupStr(grouper func(int, string) string) *Value { + groups := make(map[string][]string) + v.EachStr(func(index int, val string) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]string, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceStr uses the specified function to replace each strings +// by iterating each item. The data in the returned result will be a +// []string containing the replaced items. +func (v *Value) ReplaceStr(replacer func(int, string) string) *Value { + arr := v.MustStrSlice() + replaced := make([]string, len(arr)) + v.EachStr(func(index int, val string) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectStr uses the specified collector function to collect a value +// for each of the strings in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectStr(collector func(int, string) interface{}) *Value { + arr := v.MustStrSlice() + collected := make([]interface{}, len(arr)) + v.EachStr(func(index int, val string) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int (int and []int) +*/ + +// Int gets the value as a int, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int(optionalDefault ...int) int { + if s, ok := v.data.(int); ok { + return s + } + if s, ok := v.data.(float64); ok { + if float64(int(s)) == s { + return int(s) + } + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt gets the value as a int. +// +// Panics if the object is not a int. +func (v *Value) MustInt() int { + if s, ok := v.data.(float64); ok { + if float64(int(s)) == s { + return int(s) + } + } + return v.data.(int) +} + +// IntSlice gets the value as a []int, returns the optionalDefault +// value or nil if the value is not a []int. +func (v *Value) IntSlice(optionalDefault ...[]int) []int { + if s, ok := v.data.([]int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustIntSlice gets the value as a []int. +// +// Panics if the object is not a []int. +func (v *Value) MustIntSlice() []int { + return v.data.([]int) +} + +// IsInt gets whether the object contained is a int or not. +func (v *Value) IsInt() bool { + _, ok := v.data.(int) + return ok +} + +// IsIntSlice gets whether the object contained is a []int or not. +func (v *Value) IsIntSlice() bool { + _, ok := v.data.([]int) + return ok +} + +// EachInt calls the specified callback for each object +// in the []int. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt(callback func(int, int) bool) *Value { + for index, val := range v.MustIntSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt uses the specified decider function to select items +// from the []int. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt(decider func(int, int) bool) *Value { + var selected []int + v.EachInt(func(index int, val int) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int. +func (v *Value) GroupInt(grouper func(int, int) string) *Value { + groups := make(map[string][]int) + v.EachInt(func(index int, val int) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt uses the specified function to replace each ints +// by iterating each item. The data in the returned result will be a +// []int containing the replaced items. +func (v *Value) ReplaceInt(replacer func(int, int) int) *Value { + arr := v.MustIntSlice() + replaced := make([]int, len(arr)) + v.EachInt(func(index int, val int) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt uses the specified collector function to collect a value +// for each of the ints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt(collector func(int, int) interface{}) *Value { + arr := v.MustIntSlice() + collected := make([]interface{}, len(arr)) + v.EachInt(func(index int, val int) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int8 (int8 and []int8) +*/ + +// Int8 gets the value as a int8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int8(optionalDefault ...int8) int8 { + if s, ok := v.data.(int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt8 gets the value as a int8. +// +// Panics if the object is not a int8. +func (v *Value) MustInt8() int8 { + return v.data.(int8) +} + +// Int8Slice gets the value as a []int8, returns the optionalDefault +// value or nil if the value is not a []int8. +func (v *Value) Int8Slice(optionalDefault ...[]int8) []int8 { + if s, ok := v.data.([]int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt8Slice gets the value as a []int8. +// +// Panics if the object is not a []int8. +func (v *Value) MustInt8Slice() []int8 { + return v.data.([]int8) +} + +// IsInt8 gets whether the object contained is a int8 or not. +func (v *Value) IsInt8() bool { + _, ok := v.data.(int8) + return ok +} + +// IsInt8Slice gets whether the object contained is a []int8 or not. +func (v *Value) IsInt8Slice() bool { + _, ok := v.data.([]int8) + return ok +} + +// EachInt8 calls the specified callback for each object +// in the []int8. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt8(callback func(int, int8) bool) *Value { + for index, val := range v.MustInt8Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt8 uses the specified decider function to select items +// from the []int8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt8(decider func(int, int8) bool) *Value { + var selected []int8 + v.EachInt8(func(index int, val int8) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int8. +func (v *Value) GroupInt8(grouper func(int, int8) string) *Value { + groups := make(map[string][]int8) + v.EachInt8(func(index int, val int8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt8 uses the specified function to replace each int8s +// by iterating each item. The data in the returned result will be a +// []int8 containing the replaced items. +func (v *Value) ReplaceInt8(replacer func(int, int8) int8) *Value { + arr := v.MustInt8Slice() + replaced := make([]int8, len(arr)) + v.EachInt8(func(index int, val int8) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt8 uses the specified collector function to collect a value +// for each of the int8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt8(collector func(int, int8) interface{}) *Value { + arr := v.MustInt8Slice() + collected := make([]interface{}, len(arr)) + v.EachInt8(func(index int, val int8) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int16 (int16 and []int16) +*/ + +// Int16 gets the value as a int16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int16(optionalDefault ...int16) int16 { + if s, ok := v.data.(int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt16 gets the value as a int16. +// +// Panics if the object is not a int16. +func (v *Value) MustInt16() int16 { + return v.data.(int16) +} + +// Int16Slice gets the value as a []int16, returns the optionalDefault +// value or nil if the value is not a []int16. +func (v *Value) Int16Slice(optionalDefault ...[]int16) []int16 { + if s, ok := v.data.([]int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt16Slice gets the value as a []int16. +// +// Panics if the object is not a []int16. +func (v *Value) MustInt16Slice() []int16 { + return v.data.([]int16) +} + +// IsInt16 gets whether the object contained is a int16 or not. +func (v *Value) IsInt16() bool { + _, ok := v.data.(int16) + return ok +} + +// IsInt16Slice gets whether the object contained is a []int16 or not. +func (v *Value) IsInt16Slice() bool { + _, ok := v.data.([]int16) + return ok +} + +// EachInt16 calls the specified callback for each object +// in the []int16. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt16(callback func(int, int16) bool) *Value { + for index, val := range v.MustInt16Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt16 uses the specified decider function to select items +// from the []int16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt16(decider func(int, int16) bool) *Value { + var selected []int16 + v.EachInt16(func(index int, val int16) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int16. +func (v *Value) GroupInt16(grouper func(int, int16) string) *Value { + groups := make(map[string][]int16) + v.EachInt16(func(index int, val int16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt16 uses the specified function to replace each int16s +// by iterating each item. The data in the returned result will be a +// []int16 containing the replaced items. +func (v *Value) ReplaceInt16(replacer func(int, int16) int16) *Value { + arr := v.MustInt16Slice() + replaced := make([]int16, len(arr)) + v.EachInt16(func(index int, val int16) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt16 uses the specified collector function to collect a value +// for each of the int16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt16(collector func(int, int16) interface{}) *Value { + arr := v.MustInt16Slice() + collected := make([]interface{}, len(arr)) + v.EachInt16(func(index int, val int16) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int32 (int32 and []int32) +*/ + +// Int32 gets the value as a int32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int32(optionalDefault ...int32) int32 { + if s, ok := v.data.(int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt32 gets the value as a int32. +// +// Panics if the object is not a int32. +func (v *Value) MustInt32() int32 { + return v.data.(int32) +} + +// Int32Slice gets the value as a []int32, returns the optionalDefault +// value or nil if the value is not a []int32. +func (v *Value) Int32Slice(optionalDefault ...[]int32) []int32 { + if s, ok := v.data.([]int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt32Slice gets the value as a []int32. +// +// Panics if the object is not a []int32. +func (v *Value) MustInt32Slice() []int32 { + return v.data.([]int32) +} + +// IsInt32 gets whether the object contained is a int32 or not. +func (v *Value) IsInt32() bool { + _, ok := v.data.(int32) + return ok +} + +// IsInt32Slice gets whether the object contained is a []int32 or not. +func (v *Value) IsInt32Slice() bool { + _, ok := v.data.([]int32) + return ok +} + +// EachInt32 calls the specified callback for each object +// in the []int32. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt32(callback func(int, int32) bool) *Value { + for index, val := range v.MustInt32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt32 uses the specified decider function to select items +// from the []int32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt32(decider func(int, int32) bool) *Value { + var selected []int32 + v.EachInt32(func(index int, val int32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int32. +func (v *Value) GroupInt32(grouper func(int, int32) string) *Value { + groups := make(map[string][]int32) + v.EachInt32(func(index int, val int32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt32 uses the specified function to replace each int32s +// by iterating each item. The data in the returned result will be a +// []int32 containing the replaced items. +func (v *Value) ReplaceInt32(replacer func(int, int32) int32) *Value { + arr := v.MustInt32Slice() + replaced := make([]int32, len(arr)) + v.EachInt32(func(index int, val int32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt32 uses the specified collector function to collect a value +// for each of the int32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt32(collector func(int, int32) interface{}) *Value { + arr := v.MustInt32Slice() + collected := make([]interface{}, len(arr)) + v.EachInt32(func(index int, val int32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int64 (int64 and []int64) +*/ + +// Int64 gets the value as a int64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int64(optionalDefault ...int64) int64 { + if s, ok := v.data.(int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt64 gets the value as a int64. +// +// Panics if the object is not a int64. +func (v *Value) MustInt64() int64 { + return v.data.(int64) +} + +// Int64Slice gets the value as a []int64, returns the optionalDefault +// value or nil if the value is not a []int64. +func (v *Value) Int64Slice(optionalDefault ...[]int64) []int64 { + if s, ok := v.data.([]int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt64Slice gets the value as a []int64. +// +// Panics if the object is not a []int64. +func (v *Value) MustInt64Slice() []int64 { + return v.data.([]int64) +} + +// IsInt64 gets whether the object contained is a int64 or not. +func (v *Value) IsInt64() bool { + _, ok := v.data.(int64) + return ok +} + +// IsInt64Slice gets whether the object contained is a []int64 or not. +func (v *Value) IsInt64Slice() bool { + _, ok := v.data.([]int64) + return ok +} + +// EachInt64 calls the specified callback for each object +// in the []int64. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt64(callback func(int, int64) bool) *Value { + for index, val := range v.MustInt64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt64 uses the specified decider function to select items +// from the []int64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt64(decider func(int, int64) bool) *Value { + var selected []int64 + v.EachInt64(func(index int, val int64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int64. +func (v *Value) GroupInt64(grouper func(int, int64) string) *Value { + groups := make(map[string][]int64) + v.EachInt64(func(index int, val int64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt64 uses the specified function to replace each int64s +// by iterating each item. The data in the returned result will be a +// []int64 containing the replaced items. +func (v *Value) ReplaceInt64(replacer func(int, int64) int64) *Value { + arr := v.MustInt64Slice() + replaced := make([]int64, len(arr)) + v.EachInt64(func(index int, val int64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt64 uses the specified collector function to collect a value +// for each of the int64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt64(collector func(int, int64) interface{}) *Value { + arr := v.MustInt64Slice() + collected := make([]interface{}, len(arr)) + v.EachInt64(func(index int, val int64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint (uint and []uint) +*/ + +// Uint gets the value as a uint, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint(optionalDefault ...uint) uint { + if s, ok := v.data.(uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint gets the value as a uint. +// +// Panics if the object is not a uint. +func (v *Value) MustUint() uint { + return v.data.(uint) +} + +// UintSlice gets the value as a []uint, returns the optionalDefault +// value or nil if the value is not a []uint. +func (v *Value) UintSlice(optionalDefault ...[]uint) []uint { + if s, ok := v.data.([]uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintSlice gets the value as a []uint. +// +// Panics if the object is not a []uint. +func (v *Value) MustUintSlice() []uint { + return v.data.([]uint) +} + +// IsUint gets whether the object contained is a uint or not. +func (v *Value) IsUint() bool { + _, ok := v.data.(uint) + return ok +} + +// IsUintSlice gets whether the object contained is a []uint or not. +func (v *Value) IsUintSlice() bool { + _, ok := v.data.([]uint) + return ok +} + +// EachUint calls the specified callback for each object +// in the []uint. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint(callback func(int, uint) bool) *Value { + for index, val := range v.MustUintSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint uses the specified decider function to select items +// from the []uint. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint(decider func(int, uint) bool) *Value { + var selected []uint + v.EachUint(func(index int, val uint) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint. +func (v *Value) GroupUint(grouper func(int, uint) string) *Value { + groups := make(map[string][]uint) + v.EachUint(func(index int, val uint) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint uses the specified function to replace each uints +// by iterating each item. The data in the returned result will be a +// []uint containing the replaced items. +func (v *Value) ReplaceUint(replacer func(int, uint) uint) *Value { + arr := v.MustUintSlice() + replaced := make([]uint, len(arr)) + v.EachUint(func(index int, val uint) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint uses the specified collector function to collect a value +// for each of the uints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint(collector func(int, uint) interface{}) *Value { + arr := v.MustUintSlice() + collected := make([]interface{}, len(arr)) + v.EachUint(func(index int, val uint) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint8 (uint8 and []uint8) +*/ + +// Uint8 gets the value as a uint8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint8(optionalDefault ...uint8) uint8 { + if s, ok := v.data.(uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint8 gets the value as a uint8. +// +// Panics if the object is not a uint8. +func (v *Value) MustUint8() uint8 { + return v.data.(uint8) +} + +// Uint8Slice gets the value as a []uint8, returns the optionalDefault +// value or nil if the value is not a []uint8. +func (v *Value) Uint8Slice(optionalDefault ...[]uint8) []uint8 { + if s, ok := v.data.([]uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint8Slice gets the value as a []uint8. +// +// Panics if the object is not a []uint8. +func (v *Value) MustUint8Slice() []uint8 { + return v.data.([]uint8) +} + +// IsUint8 gets whether the object contained is a uint8 or not. +func (v *Value) IsUint8() bool { + _, ok := v.data.(uint8) + return ok +} + +// IsUint8Slice gets whether the object contained is a []uint8 or not. +func (v *Value) IsUint8Slice() bool { + _, ok := v.data.([]uint8) + return ok +} + +// EachUint8 calls the specified callback for each object +// in the []uint8. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint8(callback func(int, uint8) bool) *Value { + for index, val := range v.MustUint8Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint8 uses the specified decider function to select items +// from the []uint8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint8(decider func(int, uint8) bool) *Value { + var selected []uint8 + v.EachUint8(func(index int, val uint8) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint8. +func (v *Value) GroupUint8(grouper func(int, uint8) string) *Value { + groups := make(map[string][]uint8) + v.EachUint8(func(index int, val uint8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint8 uses the specified function to replace each uint8s +// by iterating each item. The data in the returned result will be a +// []uint8 containing the replaced items. +func (v *Value) ReplaceUint8(replacer func(int, uint8) uint8) *Value { + arr := v.MustUint8Slice() + replaced := make([]uint8, len(arr)) + v.EachUint8(func(index int, val uint8) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint8 uses the specified collector function to collect a value +// for each of the uint8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint8(collector func(int, uint8) interface{}) *Value { + arr := v.MustUint8Slice() + collected := make([]interface{}, len(arr)) + v.EachUint8(func(index int, val uint8) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint16 (uint16 and []uint16) +*/ + +// Uint16 gets the value as a uint16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint16(optionalDefault ...uint16) uint16 { + if s, ok := v.data.(uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint16 gets the value as a uint16. +// +// Panics if the object is not a uint16. +func (v *Value) MustUint16() uint16 { + return v.data.(uint16) +} + +// Uint16Slice gets the value as a []uint16, returns the optionalDefault +// value or nil if the value is not a []uint16. +func (v *Value) Uint16Slice(optionalDefault ...[]uint16) []uint16 { + if s, ok := v.data.([]uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint16Slice gets the value as a []uint16. +// +// Panics if the object is not a []uint16. +func (v *Value) MustUint16Slice() []uint16 { + return v.data.([]uint16) +} + +// IsUint16 gets whether the object contained is a uint16 or not. +func (v *Value) IsUint16() bool { + _, ok := v.data.(uint16) + return ok +} + +// IsUint16Slice gets whether the object contained is a []uint16 or not. +func (v *Value) IsUint16Slice() bool { + _, ok := v.data.([]uint16) + return ok +} + +// EachUint16 calls the specified callback for each object +// in the []uint16. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint16(callback func(int, uint16) bool) *Value { + for index, val := range v.MustUint16Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint16 uses the specified decider function to select items +// from the []uint16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint16(decider func(int, uint16) bool) *Value { + var selected []uint16 + v.EachUint16(func(index int, val uint16) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint16. +func (v *Value) GroupUint16(grouper func(int, uint16) string) *Value { + groups := make(map[string][]uint16) + v.EachUint16(func(index int, val uint16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint16 uses the specified function to replace each uint16s +// by iterating each item. The data in the returned result will be a +// []uint16 containing the replaced items. +func (v *Value) ReplaceUint16(replacer func(int, uint16) uint16) *Value { + arr := v.MustUint16Slice() + replaced := make([]uint16, len(arr)) + v.EachUint16(func(index int, val uint16) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint16 uses the specified collector function to collect a value +// for each of the uint16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint16(collector func(int, uint16) interface{}) *Value { + arr := v.MustUint16Slice() + collected := make([]interface{}, len(arr)) + v.EachUint16(func(index int, val uint16) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint32 (uint32 and []uint32) +*/ + +// Uint32 gets the value as a uint32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint32(optionalDefault ...uint32) uint32 { + if s, ok := v.data.(uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint32 gets the value as a uint32. +// +// Panics if the object is not a uint32. +func (v *Value) MustUint32() uint32 { + return v.data.(uint32) +} + +// Uint32Slice gets the value as a []uint32, returns the optionalDefault +// value or nil if the value is not a []uint32. +func (v *Value) Uint32Slice(optionalDefault ...[]uint32) []uint32 { + if s, ok := v.data.([]uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint32Slice gets the value as a []uint32. +// +// Panics if the object is not a []uint32. +func (v *Value) MustUint32Slice() []uint32 { + return v.data.([]uint32) +} + +// IsUint32 gets whether the object contained is a uint32 or not. +func (v *Value) IsUint32() bool { + _, ok := v.data.(uint32) + return ok +} + +// IsUint32Slice gets whether the object contained is a []uint32 or not. +func (v *Value) IsUint32Slice() bool { + _, ok := v.data.([]uint32) + return ok +} + +// EachUint32 calls the specified callback for each object +// in the []uint32. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint32(callback func(int, uint32) bool) *Value { + for index, val := range v.MustUint32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint32 uses the specified decider function to select items +// from the []uint32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint32(decider func(int, uint32) bool) *Value { + var selected []uint32 + v.EachUint32(func(index int, val uint32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint32. +func (v *Value) GroupUint32(grouper func(int, uint32) string) *Value { + groups := make(map[string][]uint32) + v.EachUint32(func(index int, val uint32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint32 uses the specified function to replace each uint32s +// by iterating each item. The data in the returned result will be a +// []uint32 containing the replaced items. +func (v *Value) ReplaceUint32(replacer func(int, uint32) uint32) *Value { + arr := v.MustUint32Slice() + replaced := make([]uint32, len(arr)) + v.EachUint32(func(index int, val uint32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint32 uses the specified collector function to collect a value +// for each of the uint32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint32(collector func(int, uint32) interface{}) *Value { + arr := v.MustUint32Slice() + collected := make([]interface{}, len(arr)) + v.EachUint32(func(index int, val uint32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint64 (uint64 and []uint64) +*/ + +// Uint64 gets the value as a uint64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint64(optionalDefault ...uint64) uint64 { + if s, ok := v.data.(uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint64 gets the value as a uint64. +// +// Panics if the object is not a uint64. +func (v *Value) MustUint64() uint64 { + return v.data.(uint64) +} + +// Uint64Slice gets the value as a []uint64, returns the optionalDefault +// value or nil if the value is not a []uint64. +func (v *Value) Uint64Slice(optionalDefault ...[]uint64) []uint64 { + if s, ok := v.data.([]uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint64Slice gets the value as a []uint64. +// +// Panics if the object is not a []uint64. +func (v *Value) MustUint64Slice() []uint64 { + return v.data.([]uint64) +} + +// IsUint64 gets whether the object contained is a uint64 or not. +func (v *Value) IsUint64() bool { + _, ok := v.data.(uint64) + return ok +} + +// IsUint64Slice gets whether the object contained is a []uint64 or not. +func (v *Value) IsUint64Slice() bool { + _, ok := v.data.([]uint64) + return ok +} + +// EachUint64 calls the specified callback for each object +// in the []uint64. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint64(callback func(int, uint64) bool) *Value { + for index, val := range v.MustUint64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint64 uses the specified decider function to select items +// from the []uint64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint64(decider func(int, uint64) bool) *Value { + var selected []uint64 + v.EachUint64(func(index int, val uint64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint64. +func (v *Value) GroupUint64(grouper func(int, uint64) string) *Value { + groups := make(map[string][]uint64) + v.EachUint64(func(index int, val uint64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint64 uses the specified function to replace each uint64s +// by iterating each item. The data in the returned result will be a +// []uint64 containing the replaced items. +func (v *Value) ReplaceUint64(replacer func(int, uint64) uint64) *Value { + arr := v.MustUint64Slice() + replaced := make([]uint64, len(arr)) + v.EachUint64(func(index int, val uint64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint64 uses the specified collector function to collect a value +// for each of the uint64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint64(collector func(int, uint64) interface{}) *Value { + arr := v.MustUint64Slice() + collected := make([]interface{}, len(arr)) + v.EachUint64(func(index int, val uint64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uintptr (uintptr and []uintptr) +*/ + +// Uintptr gets the value as a uintptr, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uintptr(optionalDefault ...uintptr) uintptr { + if s, ok := v.data.(uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUintptr gets the value as a uintptr. +// +// Panics if the object is not a uintptr. +func (v *Value) MustUintptr() uintptr { + return v.data.(uintptr) +} + +// UintptrSlice gets the value as a []uintptr, returns the optionalDefault +// value or nil if the value is not a []uintptr. +func (v *Value) UintptrSlice(optionalDefault ...[]uintptr) []uintptr { + if s, ok := v.data.([]uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintptrSlice gets the value as a []uintptr. +// +// Panics if the object is not a []uintptr. +func (v *Value) MustUintptrSlice() []uintptr { + return v.data.([]uintptr) +} + +// IsUintptr gets whether the object contained is a uintptr or not. +func (v *Value) IsUintptr() bool { + _, ok := v.data.(uintptr) + return ok +} + +// IsUintptrSlice gets whether the object contained is a []uintptr or not. +func (v *Value) IsUintptrSlice() bool { + _, ok := v.data.([]uintptr) + return ok +} + +// EachUintptr calls the specified callback for each object +// in the []uintptr. +// +// Panics if the object is the wrong type. +func (v *Value) EachUintptr(callback func(int, uintptr) bool) *Value { + for index, val := range v.MustUintptrSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUintptr uses the specified decider function to select items +// from the []uintptr. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUintptr(decider func(int, uintptr) bool) *Value { + var selected []uintptr + v.EachUintptr(func(index int, val uintptr) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUintptr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uintptr. +func (v *Value) GroupUintptr(grouper func(int, uintptr) string) *Value { + groups := make(map[string][]uintptr) + v.EachUintptr(func(index int, val uintptr) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uintptr, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUintptr uses the specified function to replace each uintptrs +// by iterating each item. The data in the returned result will be a +// []uintptr containing the replaced items. +func (v *Value) ReplaceUintptr(replacer func(int, uintptr) uintptr) *Value { + arr := v.MustUintptrSlice() + replaced := make([]uintptr, len(arr)) + v.EachUintptr(func(index int, val uintptr) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUintptr uses the specified collector function to collect a value +// for each of the uintptrs in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUintptr(collector func(int, uintptr) interface{}) *Value { + arr := v.MustUintptrSlice() + collected := make([]interface{}, len(arr)) + v.EachUintptr(func(index int, val uintptr) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Float32 (float32 and []float32) +*/ + +// Float32 gets the value as a float32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float32(optionalDefault ...float32) float32 { + if s, ok := v.data.(float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat32 gets the value as a float32. +// +// Panics if the object is not a float32. +func (v *Value) MustFloat32() float32 { + return v.data.(float32) +} + +// Float32Slice gets the value as a []float32, returns the optionalDefault +// value or nil if the value is not a []float32. +func (v *Value) Float32Slice(optionalDefault ...[]float32) []float32 { + if s, ok := v.data.([]float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat32Slice gets the value as a []float32. +// +// Panics if the object is not a []float32. +func (v *Value) MustFloat32Slice() []float32 { + return v.data.([]float32) +} + +// IsFloat32 gets whether the object contained is a float32 or not. +func (v *Value) IsFloat32() bool { + _, ok := v.data.(float32) + return ok +} + +// IsFloat32Slice gets whether the object contained is a []float32 or not. +func (v *Value) IsFloat32Slice() bool { + _, ok := v.data.([]float32) + return ok +} + +// EachFloat32 calls the specified callback for each object +// in the []float32. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat32(callback func(int, float32) bool) *Value { + for index, val := range v.MustFloat32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereFloat32 uses the specified decider function to select items +// from the []float32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat32(decider func(int, float32) bool) *Value { + var selected []float32 + v.EachFloat32(func(index int, val float32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupFloat32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float32. +func (v *Value) GroupFloat32(grouper func(int, float32) string) *Value { + groups := make(map[string][]float32) + v.EachFloat32(func(index int, val float32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceFloat32 uses the specified function to replace each float32s +// by iterating each item. The data in the returned result will be a +// []float32 containing the replaced items. +func (v *Value) ReplaceFloat32(replacer func(int, float32) float32) *Value { + arr := v.MustFloat32Slice() + replaced := make([]float32, len(arr)) + v.EachFloat32(func(index int, val float32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectFloat32 uses the specified collector function to collect a value +// for each of the float32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat32(collector func(int, float32) interface{}) *Value { + arr := v.MustFloat32Slice() + collected := make([]interface{}, len(arr)) + v.EachFloat32(func(index int, val float32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Float64 (float64 and []float64) +*/ + +// Float64 gets the value as a float64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float64(optionalDefault ...float64) float64 { + if s, ok := v.data.(float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat64 gets the value as a float64. +// +// Panics if the object is not a float64. +func (v *Value) MustFloat64() float64 { + return v.data.(float64) +} + +// Float64Slice gets the value as a []float64, returns the optionalDefault +// value or nil if the value is not a []float64. +func (v *Value) Float64Slice(optionalDefault ...[]float64) []float64 { + if s, ok := v.data.([]float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat64Slice gets the value as a []float64. +// +// Panics if the object is not a []float64. +func (v *Value) MustFloat64Slice() []float64 { + return v.data.([]float64) +} + +// IsFloat64 gets whether the object contained is a float64 or not. +func (v *Value) IsFloat64() bool { + _, ok := v.data.(float64) + return ok +} + +// IsFloat64Slice gets whether the object contained is a []float64 or not. +func (v *Value) IsFloat64Slice() bool { + _, ok := v.data.([]float64) + return ok +} + +// EachFloat64 calls the specified callback for each object +// in the []float64. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat64(callback func(int, float64) bool) *Value { + for index, val := range v.MustFloat64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereFloat64 uses the specified decider function to select items +// from the []float64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat64(decider func(int, float64) bool) *Value { + var selected []float64 + v.EachFloat64(func(index int, val float64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupFloat64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float64. +func (v *Value) GroupFloat64(grouper func(int, float64) string) *Value { + groups := make(map[string][]float64) + v.EachFloat64(func(index int, val float64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceFloat64 uses the specified function to replace each float64s +// by iterating each item. The data in the returned result will be a +// []float64 containing the replaced items. +func (v *Value) ReplaceFloat64(replacer func(int, float64) float64) *Value { + arr := v.MustFloat64Slice() + replaced := make([]float64, len(arr)) + v.EachFloat64(func(index int, val float64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectFloat64 uses the specified collector function to collect a value +// for each of the float64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat64(collector func(int, float64) interface{}) *Value { + arr := v.MustFloat64Slice() + collected := make([]interface{}, len(arr)) + v.EachFloat64(func(index int, val float64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Complex64 (complex64 and []complex64) +*/ + +// Complex64 gets the value as a complex64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex64(optionalDefault ...complex64) complex64 { + if s, ok := v.data.(complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex64 gets the value as a complex64. +// +// Panics if the object is not a complex64. +func (v *Value) MustComplex64() complex64 { + return v.data.(complex64) +} + +// Complex64Slice gets the value as a []complex64, returns the optionalDefault +// value or nil if the value is not a []complex64. +func (v *Value) Complex64Slice(optionalDefault ...[]complex64) []complex64 { + if s, ok := v.data.([]complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex64Slice gets the value as a []complex64. +// +// Panics if the object is not a []complex64. +func (v *Value) MustComplex64Slice() []complex64 { + return v.data.([]complex64) +} + +// IsComplex64 gets whether the object contained is a complex64 or not. +func (v *Value) IsComplex64() bool { + _, ok := v.data.(complex64) + return ok +} + +// IsComplex64Slice gets whether the object contained is a []complex64 or not. +func (v *Value) IsComplex64Slice() bool { + _, ok := v.data.([]complex64) + return ok +} + +// EachComplex64 calls the specified callback for each object +// in the []complex64. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex64(callback func(int, complex64) bool) *Value { + for index, val := range v.MustComplex64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereComplex64 uses the specified decider function to select items +// from the []complex64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex64(decider func(int, complex64) bool) *Value { + var selected []complex64 + v.EachComplex64(func(index int, val complex64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupComplex64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex64. +func (v *Value) GroupComplex64(grouper func(int, complex64) string) *Value { + groups := make(map[string][]complex64) + v.EachComplex64(func(index int, val complex64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceComplex64 uses the specified function to replace each complex64s +// by iterating each item. The data in the returned result will be a +// []complex64 containing the replaced items. +func (v *Value) ReplaceComplex64(replacer func(int, complex64) complex64) *Value { + arr := v.MustComplex64Slice() + replaced := make([]complex64, len(arr)) + v.EachComplex64(func(index int, val complex64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectComplex64 uses the specified collector function to collect a value +// for each of the complex64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex64(collector func(int, complex64) interface{}) *Value { + arr := v.MustComplex64Slice() + collected := make([]interface{}, len(arr)) + v.EachComplex64(func(index int, val complex64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Complex128 (complex128 and []complex128) +*/ + +// Complex128 gets the value as a complex128, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex128(optionalDefault ...complex128) complex128 { + if s, ok := v.data.(complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex128 gets the value as a complex128. +// +// Panics if the object is not a complex128. +func (v *Value) MustComplex128() complex128 { + return v.data.(complex128) +} + +// Complex128Slice gets the value as a []complex128, returns the optionalDefault +// value or nil if the value is not a []complex128. +func (v *Value) Complex128Slice(optionalDefault ...[]complex128) []complex128 { + if s, ok := v.data.([]complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex128Slice gets the value as a []complex128. +// +// Panics if the object is not a []complex128. +func (v *Value) MustComplex128Slice() []complex128 { + return v.data.([]complex128) +} + +// IsComplex128 gets whether the object contained is a complex128 or not. +func (v *Value) IsComplex128() bool { + _, ok := v.data.(complex128) + return ok +} + +// IsComplex128Slice gets whether the object contained is a []complex128 or not. +func (v *Value) IsComplex128Slice() bool { + _, ok := v.data.([]complex128) + return ok +} + +// EachComplex128 calls the specified callback for each object +// in the []complex128. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex128(callback func(int, complex128) bool) *Value { + for index, val := range v.MustComplex128Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereComplex128 uses the specified decider function to select items +// from the []complex128. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex128(decider func(int, complex128) bool) *Value { + var selected []complex128 + v.EachComplex128(func(index int, val complex128) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupComplex128 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex128. +func (v *Value) GroupComplex128(grouper func(int, complex128) string) *Value { + groups := make(map[string][]complex128) + v.EachComplex128(func(index int, val complex128) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex128, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceComplex128 uses the specified function to replace each complex128s +// by iterating each item. The data in the returned result will be a +// []complex128 containing the replaced items. +func (v *Value) ReplaceComplex128(replacer func(int, complex128) complex128) *Value { + arr := v.MustComplex128Slice() + replaced := make([]complex128, len(arr)) + v.EachComplex128(func(index int, val complex128) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectComplex128 uses the specified collector function to collect a value +// for each of the complex128s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex128(collector func(int, complex128) interface{}) *Value { + arr := v.MustComplex128Slice() + collected := make([]interface{}, len(arr)) + v.EachComplex128(func(index int, val complex128) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} diff --git a/vendor/github.com/stretchr/objx/value.go b/vendor/github.com/stretchr/objx/value.go new file mode 100644 index 000000000..4e5f9b77e --- /dev/null +++ b/vendor/github.com/stretchr/objx/value.go @@ -0,0 +1,159 @@ +package objx + +import ( + "fmt" + "strconv" +) + +// Value provides methods for extracting interface{} data in various +// types. +type Value struct { + // data contains the raw data being managed by this Value + data interface{} +} + +// Data returns the raw data contained by this Value +func (v *Value) Data() interface{} { + return v.data +} + +// String returns the value always as a string +func (v *Value) String() string { + switch { + case v.IsNil(): + return "" + case v.IsStr(): + return v.Str() + case v.IsBool(): + return strconv.FormatBool(v.Bool()) + case v.IsFloat32(): + return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32) + case v.IsFloat64(): + return strconv.FormatFloat(v.Float64(), 'f', -1, 64) + case v.IsInt(): + return strconv.FormatInt(int64(v.Int()), 10) + case v.IsInt8(): + return strconv.FormatInt(int64(v.Int8()), 10) + case v.IsInt16(): + return strconv.FormatInt(int64(v.Int16()), 10) + case v.IsInt32(): + return strconv.FormatInt(int64(v.Int32()), 10) + case v.IsInt64(): + return strconv.FormatInt(v.Int64(), 10) + case v.IsUint(): + return strconv.FormatUint(uint64(v.Uint()), 10) + case v.IsUint8(): + return strconv.FormatUint(uint64(v.Uint8()), 10) + case v.IsUint16(): + return strconv.FormatUint(uint64(v.Uint16()), 10) + case v.IsUint32(): + return strconv.FormatUint(uint64(v.Uint32()), 10) + case v.IsUint64(): + return strconv.FormatUint(v.Uint64(), 10) + } + return fmt.Sprintf("%#v", v.Data()) +} + +// StringSlice returns the value always as a []string +func (v *Value) StringSlice(optionalDefault ...[]string) []string { + switch { + case v.IsStrSlice(): + return v.MustStrSlice() + case v.IsBoolSlice(): + slice := v.MustBoolSlice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatBool(iv) + } + return vals + case v.IsFloat32Slice(): + slice := v.MustFloat32Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatFloat(float64(iv), 'f', -1, 32) + } + return vals + case v.IsFloat64Slice(): + slice := v.MustFloat64Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatFloat(iv, 'f', -1, 64) + } + return vals + case v.IsIntSlice(): + slice := v.MustIntSlice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatInt(int64(iv), 10) + } + return vals + case v.IsInt8Slice(): + slice := v.MustInt8Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatInt(int64(iv), 10) + } + return vals + case v.IsInt16Slice(): + slice := v.MustInt16Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatInt(int64(iv), 10) + } + return vals + case v.IsInt32Slice(): + slice := v.MustInt32Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatInt(int64(iv), 10) + } + return vals + case v.IsInt64Slice(): + slice := v.MustInt64Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatInt(iv, 10) + } + return vals + case v.IsUintSlice(): + slice := v.MustUintSlice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatUint(uint64(iv), 10) + } + return vals + case v.IsUint8Slice(): + slice := v.MustUint8Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatUint(uint64(iv), 10) + } + return vals + case v.IsUint16Slice(): + slice := v.MustUint16Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatUint(uint64(iv), 10) + } + return vals + case v.IsUint32Slice(): + slice := v.MustUint32Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatUint(uint64(iv), 10) + } + return vals + case v.IsUint64Slice(): + slice := v.MustUint64Slice() + vals := make([]string, len(slice)) + for i, iv := range slice { + vals[i] = strconv.FormatUint(iv, 10) + } + return vals + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + + return []string{} +} diff --git a/vendor/github.com/stretchr/testify/mock/doc.go b/vendor/github.com/stretchr/testify/mock/doc.go new file mode 100644 index 000000000..d6b3c844c --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/doc.go @@ -0,0 +1,44 @@ +// Package mock provides a system by which it is possible to mock your objects +// and verify calls are happening as expected. +// +// # Example Usage +// +// The mock package provides an object, Mock, that tracks activity on another object. It is usually +// embedded into a test object as shown below: +// +// type MyTestObject struct { +// // add a Mock object instance +// mock.Mock +// +// // other fields go here as normal +// } +// +// When implementing the methods of an interface, you wire your functions up +// to call the Mock.Called(args...) method, and return the appropriate values. +// +// For example, to mock a method that saves the name and age of a person and returns +// the year of their birth or an error, you might write this: +// +// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { +// args := o.Called(firstname, lastname, age) +// return args.Int(0), args.Error(1) +// } +// +// The Int, Error and Bool methods are examples of strongly typed getters that take the argument +// index position. Given this argument list: +// +// (12, true, "Something") +// +// You could read them out strongly typed like this: +// +// args.Int(0) +// args.Bool(1) +// args.String(2) +// +// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: +// +// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) +// +// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those +// cases you should check for nil first. +package mock diff --git a/vendor/github.com/stretchr/testify/mock/mock.go b/vendor/github.com/stretchr/testify/mock/mock.go new file mode 100644 index 000000000..f4b42e44f --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/mock.go @@ -0,0 +1,1226 @@ +package mock + +import ( + "errors" + "fmt" + "path" + "reflect" + "regexp" + "runtime" + "strings" + "sync" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/objx" + + "github.com/stretchr/testify/assert" +) + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Logf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + FailNow() +} + +/* + Call +*/ + +// Call represents a method call and is used for setting expectations, +// as well as recording activity. +type Call struct { + Parent *Mock + + // The name of the method that was or will be called. + Method string + + // Holds the arguments of the method. + Arguments Arguments + + // Holds the arguments that should be returned when + // this method is called. + ReturnArguments Arguments + + // Holds the caller info for the On() call + callerInfo []string + + // The number of times to return the return arguments when setting + // expectations. 0 means to always return the value. + Repeatability int + + // Amount of times this call has been called + totalCalls int + + // Call to this method can be optional + optional bool + + // Holds a channel that will be used to block the Return until it either + // receives a message or is closed. nil means it returns immediately. + WaitFor <-chan time.Time + + waitTime time.Duration + + // Holds a handler used to manipulate arguments content that are passed by + // reference. It's useful when mocking methods such as unmarshalers or + // decoders. + RunFn func(Arguments) + + // PanicMsg holds msg to be used to mock panic on the function call + // if the PanicMsg is set to a non nil string the function call will panic + // irrespective of other settings + PanicMsg *string + + // Calls which must be satisfied before this call can be + requires []*Call +} + +func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call { + return &Call{ + Parent: parent, + Method: methodName, + Arguments: methodArguments, + ReturnArguments: make([]interface{}, 0), + callerInfo: callerInfo, + Repeatability: 0, + WaitFor: nil, + RunFn: nil, + PanicMsg: nil, + } +} + +func (c *Call) lock() { + c.Parent.mutex.Lock() +} + +func (c *Call) unlock() { + c.Parent.mutex.Unlock() +} + +// Return specifies the return arguments for the expectation. +// +// Mock.On("DoSomething").Return(errors.New("failed")) +func (c *Call) Return(returnArguments ...interface{}) *Call { + c.lock() + defer c.unlock() + + c.ReturnArguments = returnArguments + + return c +} + +// Panic specifies if the functon call should fail and the panic message +// +// Mock.On("DoSomething").Panic("test panic") +func (c *Call) Panic(msg string) *Call { + c.lock() + defer c.unlock() + + c.PanicMsg = &msg + + return c +} + +// Once indicates that that the mock should only return the value once. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() +func (c *Call) Once() *Call { + return c.Times(1) +} + +// Twice indicates that that the mock should only return the value twice. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() +func (c *Call) Twice() *Call { + return c.Times(2) +} + +// Times indicates that that the mock should only return the indicated number +// of times. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) +func (c *Call) Times(i int) *Call { + c.lock() + defer c.unlock() + c.Repeatability = i + return c +} + +// WaitUntil sets the channel that will block the mock's return until its closed +// or a message is received. +// +// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second)) +func (c *Call) WaitUntil(w <-chan time.Time) *Call { + c.lock() + defer c.unlock() + c.WaitFor = w + return c +} + +// After sets how long to block until the call returns +// +// Mock.On("MyMethod", arg1, arg2).After(time.Second) +func (c *Call) After(d time.Duration) *Call { + c.lock() + defer c.unlock() + c.waitTime = d + return c +} + +// Run sets a handler to be called before returning. It can be used when +// mocking a method (such as an unmarshaler) that takes a pointer to a struct and +// sets properties in such struct +// +// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}")).Return().Run(func(args Arguments) { +// arg := args.Get(0).(*map[string]interface{}) +// arg["foo"] = "bar" +// }) +func (c *Call) Run(fn func(args Arguments)) *Call { + c.lock() + defer c.unlock() + c.RunFn = fn + return c +} + +// Maybe allows the method call to be optional. Not calling an optional method +// will not cause an error while asserting expectations +func (c *Call) Maybe() *Call { + c.lock() + defer c.unlock() + c.optional = true + return c +} + +// On chains a new expectation description onto the mocked interface. This +// allows syntax like. +// +// Mock. +// On("MyMethod", 1).Return(nil). +// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) +// +//go:noinline +func (c *Call) On(methodName string, arguments ...interface{}) *Call { + return c.Parent.On(methodName, arguments...) +} + +// Unset removes a mock handler from being called. +// +// test.On("func", mock.Anything).Unset() +func (c *Call) Unset() *Call { + var unlockOnce sync.Once + + for _, arg := range c.Arguments { + if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { + panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) + } + } + + c.lock() + defer unlockOnce.Do(c.unlock) + + foundMatchingCall := false + + // in-place filter slice for calls to be removed - iterate from 0'th to last skipping unnecessary ones + var index int // write index + for _, call := range c.Parent.ExpectedCalls { + if call.Method == c.Method { + _, diffCount := call.Arguments.Diff(c.Arguments) + if diffCount == 0 { + foundMatchingCall = true + // Remove from ExpectedCalls - just skip it + continue + } + } + c.Parent.ExpectedCalls[index] = call + index++ + } + // trim slice up to last copied index + c.Parent.ExpectedCalls = c.Parent.ExpectedCalls[:index] + + if !foundMatchingCall { + unlockOnce.Do(c.unlock) + c.Parent.fail("\n\nmock: Could not find expected call\n-----------------------------\n\n%s\n\n", + callString(c.Method, c.Arguments, true), + ) + } + + return c +} + +// NotBefore indicates that the mock should only be called after the referenced +// calls have been called as expected. The referenced calls may be from the +// same mock instance and/or other mock instances. +// +// Mock.On("Do").Return(nil).Notbefore( +// Mock.On("Init").Return(nil) +// ) +func (c *Call) NotBefore(calls ...*Call) *Call { + c.lock() + defer c.unlock() + + for _, call := range calls { + if call.Parent == nil { + panic("not before calls must be created with Mock.On()") + } + } + + c.requires = append(c.requires, calls...) + return c +} + +// Mock is the workhorse used to track activity on another object. +// For an example of its usage, refer to the "Example Usage" section at the top +// of this document. +type Mock struct { + // Represents the calls that are expected of + // an object. + ExpectedCalls []*Call + + // Holds the calls that were made to this mocked object. + Calls []Call + + // test is An optional variable that holds the test struct, to be used when an + // invalid mock call was made. + test TestingT + + // TestData holds any data that might be useful for testing. Testify ignores + // this data completely allowing you to do whatever you like with it. + testData objx.Map + + mutex sync.Mutex +} + +// String provides a %v format string for Mock. +// Note: this is used implicitly by Arguments.Diff if a Mock is passed. +// It exists because go's default %v formatting traverses the struct +// without acquiring the mutex, which is detected by go test -race. +func (m *Mock) String() string { + return fmt.Sprintf("%[1]T<%[1]p>", m) +} + +// TestData holds any data that might be useful for testing. Testify ignores +// this data completely allowing you to do whatever you like with it. +func (m *Mock) TestData() objx.Map { + if m.testData == nil { + m.testData = make(objx.Map) + } + + return m.testData +} + +/* + Setting expectations +*/ + +// Test sets the test struct variable of the mock object +func (m *Mock) Test(t TestingT) { + m.mutex.Lock() + defer m.mutex.Unlock() + m.test = t +} + +// fail fails the current test with the given formatted format and args. +// In case that a test was defined, it uses the test APIs for failing a test, +// otherwise it uses panic. +func (m *Mock) fail(format string, args ...interface{}) { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.test == nil { + panic(fmt.Sprintf(format, args...)) + } + m.test.Errorf(format, args...) + m.test.FailNow() +} + +// On starts a description of an expectation of the specified method +// being called. +// +// Mock.On("MyMethod", arg1, arg2) +func (m *Mock) On(methodName string, arguments ...interface{}) *Call { + for _, arg := range arguments { + if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { + panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) + } + } + + m.mutex.Lock() + defer m.mutex.Unlock() + c := newCall(m, methodName, assert.CallerInfo(), arguments...) + m.ExpectedCalls = append(m.ExpectedCalls, c) + return c +} + +// /* +// Recording and responding to activity +// */ + +func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) { + var expectedCall *Call + + for i, call := range m.ExpectedCalls { + if call.Method == method { + _, diffCount := call.Arguments.Diff(arguments) + if diffCount == 0 { + expectedCall = call + if call.Repeatability > -1 { + return i, call + } + } + } + } + + return -1, expectedCall +} + +type matchCandidate struct { + call *Call + mismatch string + diffCount int +} + +func (c matchCandidate) isBetterMatchThan(other matchCandidate) bool { + if c.call == nil { + return false + } + if other.call == nil { + return true + } + + if c.diffCount > other.diffCount { + return false + } + if c.diffCount < other.diffCount { + return true + } + + if c.call.Repeatability > 0 && other.call.Repeatability <= 0 { + return true + } + return false +} + +func (m *Mock) findClosestCall(method string, arguments ...interface{}) (*Call, string) { + var bestMatch matchCandidate + + for _, call := range m.expectedCalls() { + if call.Method == method { + + errInfo, tempDiffCount := call.Arguments.Diff(arguments) + tempCandidate := matchCandidate{ + call: call, + mismatch: errInfo, + diffCount: tempDiffCount, + } + if tempCandidate.isBetterMatchThan(bestMatch) { + bestMatch = tempCandidate + } + } + } + + return bestMatch.call, bestMatch.mismatch +} + +func callString(method string, arguments Arguments, includeArgumentValues bool) string { + var argValsString string + if includeArgumentValues { + var argVals []string + for argIndex, arg := range arguments { + if _, ok := arg.(*FunctionalOptionsArgument); ok { + argVals = append(argVals, fmt.Sprintf("%d: %s", argIndex, arg)) + continue + } + argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg)) + } + argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t")) + } + + return fmt.Sprintf("%s(%s)%s", method, arguments.String(), argValsString) +} + +// Called tells the mock object that a method has been called, and gets an array +// of arguments to return. Panics if the call is unexpected (i.e. not preceded by +// appropriate .On .Return() calls) +// If Call.WaitFor is set, blocks until the channel is closed or receives a message. +func (m *Mock) Called(arguments ...interface{}) Arguments { + // get the calling function's name + pc, _, _, ok := runtime.Caller(1) + if !ok { + panic("Couldn't get the caller information") + } + functionPath := runtime.FuncForPC(pc).Name() + // Next four lines are required to use GCCGO function naming conventions. + // For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock + // uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree + // With GCCGO we need to remove interface information starting from pN