SDL CLI 'remove' -command key search pattern 29/7129/2
authorTimo Tietavainen <timo.tietavainen@nokia.com>
Fri, 26 Nov 2021 01:51:31 +0000 (03:51 +0200)
committerTimo Tietavainen <timo.tietavainen@nokia.com>
Fri, 26 Nov 2021 14:24:57 +0000 (16:24 +0200)
Implement a key search pattern support for the sdlcli 'remove' -command.
With this command keys can be removed from SDL DB, removed keys can be
defined for the command either as key name or key search pattern
arguments. Command removes all the SDL DB keys by default, if key or key
search pattern argument is not given for the command.
Command syntax is:
  sdlcli remove <namespace> [<key|pattern>... <keyN|patternN>] [flags]

Issue-Id: RIC-113

Signed-off-by: Timo Tietavainen <timo.tietavainen@nokia.com>
Change-Id: I2cbe20a45aa21a15ee8a8b79abb7667b0307a93f

internal/cli/remove.go
internal/cli/remove_test.go

index 35fef03..4e69ff4 100644 (file)
@@ -30,9 +30,29 @@ import (
        "os"
 )
 
-const removeLongHelp = "Remove key(s) under given namespace from SDL DB.\n" +
-       "Multiple keys to remove can be given in a single command,\n" +
-       "each key is separated from the one next to it by a space."
+const removeLongHelp = "Remove key(s) under given namespace from SDL DB. A key to remove\n" +
+       "can be an exact key name or a key search pattern. Multiple key names\n" +
+       "or search patterns can be given in a single command, each of which\n" +
+       "is separated from the one next to it by a space. If no key or\n" +
+       "search pattern is given, command removes all the keys under given\n" +
+       "namespace."
+
+const removeExample = "  # Remove all keys in the given namespace.\n" +
+       "  sdlcli remove sdlns\n" +
+       "  # Remove keys 'key1' and 'key2' in the given namespace.\n" +
+       "  sdlcli remove sdlns key1 key2\n" +
+       "  # Remove keys in the given namespace matching given key search pattern.\n" +
+       "  sdlcli remove sdlns 'he*'\n\n" +
+
+       "  Supported search glob-style patterns:\n" +
+       "    h?llo matches hello, hallo and hxllo\n" +
+       "    h*llo matches hllo and heeeello\n" +
+       "    h[ae]llo matches hello and hallo, but not hillo\n" +
+       "    h[^e]llo matches hallo, hbllo, ... but not hello\n" +
+       "    h[a-b]llo matches hallo and hbllo\n\n" +
+       "  The \\ escapes character in key search pattern and those will be\n" +
+       "  treated as a normal character:\n" +
+       "    h\\[?llo\\* matches h[ello* and h[allo"
 
 func init() {
        rootCmd.AddCommand(newRemoveCmd(func() ISyncStorage {
@@ -42,10 +62,11 @@ func init() {
 
 func newRemoveCmd(sdlCreateCb SyncStorageCreateCb) *cobra.Command {
        cmd := &cobra.Command{
-               Use:   "remove <namespace> <key> [<key2>... <keyN>]",
-               Short: "Remove key(s) under given namespace from SDL DB",
-               Long:  removeLongHelp,
-               Args:  cobra.MinimumNArgs(2),
+               Use:     "remove <namespace> [<key|pattern>... <keyN|patternN>]",
+               Short:   "Remove key(s) under given namespace from SDL DB",
+               Long:    removeLongHelp,
+               Example: removeExample,
+               Args:    cobra.MinimumNArgs(1),
                RunE: func(cmd *cobra.Command, args []string) error {
                        var buf bytes.Buffer
                        sdlgoredis.SetDbLogger(&buf)
@@ -62,8 +83,19 @@ func newRemoveCmd(sdlCreateCb SyncStorageCreateCb) *cobra.Command {
 
 func runRemove(sdlCreateCb SyncStorageCreateCb, args []string) error {
        sdl := sdlCreateCb()
-       if err := sdl.Remove(args[0], args[1:]); err != nil {
-               return err
+       ns := args[0]
+       keyPatterns := []string{"*"}
+       if len(args) > 1 {
+               keyPatterns = args[1:]
+       }
+       for _, keyPattern := range keyPatterns {
+               keys, err := sdl.ListKeys(ns, keyPattern)
+               if err != nil {
+                       return err
+               }
+               if err := sdl.Remove(ns, keys); err != nil {
+                       return err
+               }
        }
        return nil
 }
index 180499d..4d23a72 100644 (file)
@@ -34,22 +34,32 @@ import (
 var removeMocks *RemoveMocks
 
 type RemoveMocks struct {
-       sdlIface *mocks.MockSdlApi
-       ns       string
-       keys     []string
-       ret      error
+       sdlIface  *mocks.MockSdlApi
+       ns        string
+       kps       []string
+       keys      []string
+       retList   error
+       retRemove error
 }
 
-func setupRemoveCliMock(ns string, keys []string, ret error) {
+func setupRemoveCliMock(ns string, keyPattern, keys []string, retList, retRemove error) {
        removeMocks = new(RemoveMocks)
        removeMocks.ns = ns
+       removeMocks.kps = keyPattern
        removeMocks.keys = keys
-       removeMocks.ret = ret
+       removeMocks.retList = retList
+       removeMocks.retRemove = retRemove
 }
 
 func newMockSdlRemoveApi() cli.ISyncStorage {
        removeMocks.sdlIface = new(mocks.MockSdlApi)
-       removeMocks.sdlIface.On("Remove", removeMocks.ns, removeMocks.keys).Return(removeMocks.ret)
+       if len(removeMocks.kps) == 0 {
+               removeMocks.kps = append(removeMocks.kps, "*")
+       }
+       for _, kp := range removeMocks.kps {
+               removeMocks.sdlIface.On("ListKeys", removeMocks.ns, kp).Return(removeMocks.keys, removeMocks.retList)
+       }
+       removeMocks.sdlIface.On("Remove", removeMocks.ns, removeMocks.keys).Return(removeMocks.retRemove).Maybe()
        return removeMocks.sdlIface
 }
 
@@ -60,7 +70,7 @@ func runRemoveCli() (string, string, error) {
        cmd.SetOut(bufStdout)
        cmd.SetErr(bufStderr)
        args := []string{removeMocks.ns}
-       args = append(args, removeMocks.keys...)
+       args = append(args, removeMocks.kps...)
        cmd.SetArgs(args)
        err := cmd.Execute()
 
@@ -69,9 +79,9 @@ func runRemoveCli() (string, string, error) {
 
 func TestCliRemoveCanShowHelp(t *testing.T) {
        var expOkErr error
-       expHelp := "Usage:\n  " + "remove <namespace> <key> [<key2>... <keyN>] [flags]"
+       expHelp := "remove <namespace> [<key|pattern>... <keyN|patternN>] [flags]"
        expFlagErr := fmt.Errorf("unknown flag: --some-unknown-flag")
-       expArgCntLtErr := fmt.Errorf("requires at least 2 arg(s), only received 1")
+       expArgCntLtErr := fmt.Errorf("requires at least 1 arg(s), only received 0")
        tests := []struct {
                args      []string
                expErr    error
@@ -80,7 +90,7 @@ func TestCliRemoveCanShowHelp(t *testing.T) {
                {args: []string{"-h"}, expErr: expOkErr, expOutput: expHelp},
                {args: []string{"--help"}, expErr: expOkErr, expOutput: expHelp},
                {args: []string{"--some-unknown-flag"}, expErr: expFlagErr, expOutput: expHelp},
-               {args: []string{"some-ns"}, expErr: expArgCntLtErr, expOutput: expHelp},
+               {args: nil, expErr: expArgCntLtErr, expOutput: expHelp},
        }
 
        for _, test := range tests {
@@ -98,7 +108,7 @@ func TestCliRemoveCanShowHelp(t *testing.T) {
 }
 
 func TestCliRemoveCommandWithOneKeySuccess(t *testing.T) {
-       setupRemoveCliMock("some-ns", []string{"some-key"}, nil)
+       setupRemoveCliMock("some-ns", []string{"some-key"}, []string{"some-key"}, nil, nil)
 
        stdout, stderr, err := runRemoveCli()
 
@@ -108,8 +118,8 @@ func TestCliRemoveCommandWithOneKeySuccess(t *testing.T) {
        removeMocks.sdlIface.AssertExpectations(t)
 }
 
-func TestCliRemoveCommandWithMultipleKeysSuccess(t *testing.T) {
-       setupRemoveCliMock("some-ns", []string{"some-key-1", "some-key-1", "some-key-3"}, nil)
+func TestCliRemoveCommandWithOneKeyPatternSuccess(t *testing.T) {
+       setupRemoveCliMock("some-ns", []string{"some-key*"}, []string{"some-key-1", "some-key-1", "some-key-3"}, nil, nil)
 
        stdout, stderr, err := runRemoveCli()
 
@@ -119,9 +129,42 @@ func TestCliRemoveCommandWithMultipleKeysSuccess(t *testing.T) {
        removeMocks.sdlIface.AssertExpectations(t)
 }
 
-func TestCliRemoveCommandFailure(t *testing.T) {
+func TestCliRemoveCommandWithMultipleKeyPatternsSuccess(t *testing.T) {
+       setupRemoveCliMock("some-ns", []string{"some-key*", "other-key*"}, []string{"other-key2"}, nil, nil)
+
+       stdout, stderr, err := runRemoveCli()
+
+       assert.Nil(t, err)
+       assert.Equal(t, "", stdout)
+       assert.Equal(t, "", stderr)
+       removeMocks.sdlIface.AssertExpectations(t)
+}
+
+func TestCliRemoveCommandWithOutKeyOrPatternSuccess(t *testing.T) {
+       setupRemoveCliMock("some-ns", []string{}, []string{"some-key-1", "some-key-1", "some-key-3"}, nil, nil)
+
+       stdout, stderr, err := runRemoveCli()
+
+       assert.Nil(t, err)
+       assert.Equal(t, "", stdout)
+       assert.Equal(t, "", stderr)
+       removeMocks.sdlIface.AssertExpectations(t)
+}
+
+func TestCliRemoveCommandErrorInSdlApiListKeysFailure(t *testing.T) {
+       expErr := fmt.Errorf("some-error")
+       setupRemoveCliMock("some-ns", []string{"*"}, []string{"some-key"}, expErr, nil)
+
+       _, stderr, err := runRemoveCli()
+
+       assert.Equal(t, expErr, err)
+       assert.Contains(t, stderr, expErr.Error())
+       removeMocks.sdlIface.AssertExpectations(t)
+}
+
+func TestCliRemoveCommandErrorInSdlApiRemoveFailure(t *testing.T) {
        expErr := fmt.Errorf("some-error")
-       setupRemoveCliMock("some-ns", []string{"some-key"}, expErr)
+       setupRemoveCliMock("some-ns", []string{"*"}, []string{"some-key"}, nil, expErr)
 
        _, stderr, err := runRemoveCli()