diff --git a/doc/news/_preparation_next_release.md b/doc/news/_preparation_next_release.md index f488624da2c..d79512271c9 100755 --- a/doc/news/_preparation_next_release.md +++ b/doc/news/_preparation_next_release.md @@ -291,15 +291,15 @@ This section keeps you up-to-date with the multi-language support provided by El ## Tools -### <> +### elektrad -- <> +- Implemented new request to add multiple metakeys for one key _(Tomislav Makar @tmakar)_ - <> - <> -### <> +### webd -- <> +- Implemented new request to add multiple metakeys for one key _(Tomislav Makar @tmakar)_ - <> - <> @@ -355,6 +355,8 @@ This section keeps you up-to-date with the multi-language support provided by El - Added Hannes Laimer to `AUTHORS.md` _(Hannes Laimer @hannes99)_ - Add README to tools/kdb _(Hannes Laimer @hannes99)_ - <> +- Fixed shell-recorder test in [`install-webui.md`](../tutorials/install-webui.md) _(Tomislav Makar @tmakar)_ +- Add new shell-recorder test in [`install-webui.md`](../tutorials/install-webui.md) for newly implemented request for adding multiple metakeys for one key _(Tomislav Makar @tmakar)_ - <> - <> - <> diff --git a/doc/tutorials/install-webui.md b/doc/tutorials/install-webui.md index d1055848f11..102d6afa71a 100644 --- a/doc/tutorials/install-webui.md +++ b/doc/tutorials/install-webui.md @@ -132,7 +132,7 @@ First create a new key-value pair `user:/test` and set its value to 5. This can ``` - through the rest API using curl ```sh - curl -X PUT -H "Content-Type: text/plain" --data "5" http://localhost:33333/kdb/user/test + curl -X PUT -H "Content-Type: text/plain" --data "5" http://localhost:33333/kdb/user:/test ``` The output of the commandline tool will be `Set string to "5"` if the key did not exist before. @@ -142,8 +142,8 @@ Elektrad will respond with code `200`. The command ```sh -curl http://localhost:33333/kdb/user/test -#> {"exists":true,"name":"test","path":"user/test","ls":["user/test"],"value":"5","meta":""} +curl http://localhost:33333/kdb/user:/test +#> {"exists":true,"name":"test","path":"user:/test","ls":["user:/test"],"value":"5","meta":""} ``` will now return the value of the specified key `user:/test`, which is stored in the database. @@ -165,6 +165,44 @@ will now return the value of the specified key `user:/test`, which is stored in +The command + +```sh +curl -X POST -H "Content-Type: application/json" -d '{"meta": [{"key": "metakey1", "value": "value1"},{"key": "metakey2", "value": "value2"}]}' http://localhost:33333/kdbMetaBulk/user:/test +``` + +will now create multiple metakeys at once. +In this case, it will create two (`metakey1` and `metakey2`). + +The command + +```sh +curl http://localhost:33333/kdb/user:/test +#> {"exists":true,"name":"test","path":"user:/test","ls":["user:/test"],"value":"1","meta":{"metakey1":"value1","metakey2":"value2"}} +``` + +will now also return the two metakeys. + + + +```json +{ + "exists": true, + "name": "test", + "path": "user:/test", + "ls": [ + "user:/test" + ], + "value": "5", + "meta": { + "metakey1": "value1", + "metakey2": "value2" + } +} +``` + + + ## Auth Currently, webd does not support authentication. The best way to work around diff --git a/src/tools/elektrad/helper_test.go b/src/tools/elektrad/helper_test.go index 9e8689116d0..4778e54727c 100644 --- a/src/tools/elektrad/helper_test.go +++ b/src/tools/elektrad/helper_test.go @@ -183,3 +183,22 @@ func getKey(t *testing.T, keyName string) elektra.Key { return ks.Lookup(parentKey) } + +func containsMeta(t *testing.T, keyName string, expectedMeta []keyValueBody) { + key := getKey(t, keyName) + removeKey(t, keyName) + + for _, actualMeta := range key.MetaSlice() { + metaName := actualMeta.Name() + metaValue := actualMeta.String() + + found := false + for _, expectedMeta := range expectedMeta { + if expectedMeta.Key == metaName && *expectedMeta.Value == metaValue { + found = true + } + } + + Assertf(t, found, "Expected meta name %s with value %s not found", metaName, metaValue) + } +} diff --git a/src/tools/elektrad/meta_handler.go b/src/tools/elektrad/meta_handler.go index fe57b639f4b..6aad6f6e40d 100644 --- a/src/tools/elektrad/meta_handler.go +++ b/src/tools/elektrad/meta_handler.go @@ -12,17 +12,23 @@ type keyValueBody struct { Value *string `json:"value"` } +type metaKeySet struct { + Meta []keyValueBody `json:"meta"` +} + // postMetaHandler sets a Meta value on a key if a value was passed, // and deletes the existing Meta value if not. // // Arguments: -// keyName the name of the key. URL path param. -// key the name of the metaKey. Passed through the key field of the JSON body. -// value the value of the metaKey. Passed through the `value` field of the JSON body. +// +// keyName the name of the key. URL path param. +// key the name of the metaKey. Passed through the key field of the JSON body. +// value the value of the metaKey. Passed through the `value` field of the JSON body. // // Response Code: -// 201 No Content if the request is successfull. -// 401 Bad Request if no key name was passed - or the key name is invalid. +// +// 201 No Content if the request is successful. +// 401 Bad Request if no key name was passed - or the key name is invalid. // // Example: `curl -X POST -d '{ "key": "hello", "value": "world" }' localhost:33333/kdbMeta/user/test/hello` func (s *server) postMetaHandler(w http.ResponseWriter, r *http.Request) { @@ -75,20 +81,78 @@ func (s *server) postMetaHandler(w http.ResponseWriter, r *http.Request) { ks.AppendKey(parentKey) } - if meta.Value == nil { - err = k.RemoveMeta(meta.Key) - } else { - err = k.SetMeta(meta.Key, *meta.Value) + metaKeys := []keyValueBody{meta} + if err = removeOrSetMetaKeys(k, errKey, handle, ks, metaKeySet{metaKeys}); err != nil { + writeError(w, err) + return } - if err != nil { + noContent(w) +} + +// postMetaBulkHandler sets a whole set of metadata. In case there is a metakey with empty value it deletes the existing +// Meta value. +// +// Arguments: +// +// keyName the name of the key. URL path param. +// metaSet the set of metakeys for the given keyName +// +// Response Code: +// +// 201 No Content if the request is successful. +// 401 Bad Request if no key name was passed - or the key name is invalid. +// +// Example: `curl -X POST -d '{ meta: [{"key": "hello", "value": "world"}] }' localhost:33333/kdbMetaBulk/user/test/hello` +func (s *server) postMetaBulkHandler(w http.ResponseWriter, r *http.Request) { + var metaKeySet metaKeySet + + keyName := parseKeyNameFromURL(r) + + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&metaKeySet); err != nil { writeError(w, err) return } - err = set(handle, ks, errKey) + if metaKeySet.Meta == nil { + badRequest(w) + return + } + + errKey, err := elektra.NewKey(keyName) + + if err != nil { + internalServerError(w) + return + } + + defer errKey.Close() + + parentKey, err := elektra.NewKey(keyName) if err != nil { + badRequest(w) + return + } + + defer parentKey.Close() + + handle, ks := getHandle(r) + + if _, err = handle.Get(ks, errKey); err != nil { + writeError(w, err) + return + } + + k := ks.LookupByName(keyName) + + if k == nil { + k = parentKey + ks.AppendKey(parentKey) + } + + if err = removeOrSetMetaKeys(k, errKey, handle, ks, metaKeySet); err != nil { writeError(w, err) return } @@ -96,16 +160,55 @@ func (s *server) postMetaHandler(w http.ResponseWriter, r *http.Request) { noContent(w) } +// removeOrSetMetaKeys removes or sets a set of metakeys for a given k +// +// Arguments: +// +// k the key to append metakeys too +// errKey the key to append errors too +// handle the KDB handle +// ks the KeySet the key is located in +// metaKeys the set of metakeys to append to k +// +// Return: +// +// error in case it case the set metakey operation failed +func removeOrSetMetaKeys(k elektra.Key, errKey elektra.Key, handle elektra.KDB, ks elektra.KeySet, metaKeys metaKeySet) error { + var err error + + for _, meta := range metaKeys.Meta { + if meta.Value == nil { + err = k.RemoveMeta(meta.Key) + } else { + err = k.SetMeta(meta.Key, *meta.Value) + } + + if err != nil { + return err + } + + err = set(handle, ks, errKey) + + if err != nil { + return err + } + } + + return err +} + // deleteMetaHandler deletes a Meta key. // // Arguments: -// keyName the name of the Key. -// key the name of the metaKey. Passed through the key field of the JSON body. +// +// keyName the name of the Key. +// key the name of the metaKey. Passed through the key field of the JSON body. // // Response Code: -// 201 No Content if the request is successfull. -// 401 Bad Request if no key name was passed - or the key name is invalid. -// 404 Not Found if the key was not found. +// +// 201 No Content if the request is successful. +// 401 Bad Request if no key name was passed - or the key name is invalid. +// 404 Not Found if the key was not found. // // Example: `curl -X DELETE -d '{ "key": "hello" }' localhost:33333/kdbMeta/user/test/hello` func (s *server) deleteMetaHandler(w http.ResponseWriter, r *http.Request) { diff --git a/src/tools/elektrad/meta_handler_test.go b/src/tools/elektrad/meta_handler_test.go index 12d78341470..8088fe8ace3 100644 --- a/src/tools/elektrad/meta_handler_test.go +++ b/src/tools/elektrad/meta_handler_test.go @@ -28,6 +28,30 @@ func TestPostMeta(t *testing.T) { Assert(t, key.Meta("postmeta") == value, "key has wrong meta value") } +func TestPostMetaBulk(t *testing.T) { + keyName := "user:/tests/elektrad/kdbmetabulk/post" + value := "Bulk set meta keys test value" + + metaOne := keyValueBody{ + Key: "postmetabulkone", + Value: &value, + } + metaTwo := keyValueBody{ + Key: "postmetabulktwo", + Value: &value, + } + + setupKey(t, keyName) + + metaSet := []keyValueBody{metaOne, metaTwo} + + w := testPost(t, "/kdbMetaBulk/"+keyName, metaKeySet{metaSet}) + code := w.Result().StatusCode + Assertf(t, code == http.StatusNoContent, "wrong status code: %v", code) + + containsMeta(t, keyName, metaSet) +} + func TestDeleteMetaHandler(t *testing.T) { keyName := "user:/tests/elektrad/kdbmeta/delete/test" value := "value" diff --git a/src/tools/elektrad/router.go b/src/tools/elektrad/router.go index 7766f3ad5f4..9978b2f056a 100644 --- a/src/tools/elektrad/router.go +++ b/src/tools/elektrad/router.go @@ -30,6 +30,7 @@ func setupRouter(app *server) http.Handler { // r.HandleFunc("/kdbCp/{path:.*", app.postCopyHandler).Methods("POST") r.HandleFunc("/kdbMeta/{path:.*}", app.postMetaHandler).Methods("POST") + r.HandleFunc("/kdbMetaBulk/{path:.*}", app.postMetaBulkHandler).Methods("POST") r.HandleFunc("/kdbMeta/{path:.*}", app.deleteMetaHandler).Methods("DELETE") return r diff --git a/src/tools/webd/package-lock.json b/src/tools/webd/package-lock.json index 9a2e076b29d..bec86a4c497 100644 --- a/src/tools/webd/package-lock.json +++ b/src/tools/webd/package-lock.json @@ -1,12 +1,12 @@ { "name": "@elektra-web/webd", - "version": "1.6.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@elektra-web/webd", - "version": "1.6.0", + "version": "2.0.0", "license": "SEE LICENSE IN ../../../LICENSE.md", "dependencies": { "body-parser": "^1.20.0", diff --git a/src/tools/webd/src/connector.js b/src/tools/webd/src/connector.js index e8d14de59e3..7824e4a5efd 100644 --- a/src/tools/webd/src/connector.js +++ b/src/tools/webd/src/connector.js @@ -86,6 +86,17 @@ const setmeta = (host, path, key, value) => return { status: res.status }; }); +const setmetabulk = (host, path, keySet) => + fetch(`${host}/kdbMetaBulk/${encodePath(path)}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(keySet), + }).then((res) => { + return { status: res.status }; + }); + const rmmeta = (host, path, key) => fetch(`${host}/kdbMeta/${encodePath(path)}`, { method: "DELETE", @@ -97,4 +108,15 @@ const rmmeta = (host, path, key) => return { status: res.status }; }); -export default { version, get, set, rm, mv, cp, setmeta, rmmeta, find }; +export default { + version, + get, + set, + rm, + mv, + cp, + setmeta, + setmetabulk, + rmmeta, + find, +}; diff --git a/src/tools/webd/src/routes/instances.js b/src/tools/webd/src/routes/instances.js index 35926affae8..740553d0067 100644 --- a/src/tools/webd/src/routes/instances.js +++ b/src/tools/webd/src/routes/instances.js @@ -225,4 +225,16 @@ export default function initInstanceRoutes(app) { .then(() => res.status(204).send()) .catch((err) => errorResponse(res, err)) ); + + app.route("/api/instances/:id/kdbMetaBulk/*").post((req, res) => + getInstance(req.params.id) + .then((instance) => + remoteKdb.setmetabulk(instance.host, req.params[0], req.body) + ) + .then((instanceRes) => + setSessionID(req.params.id, req.session, instanceRes) + ) + .then(() => res.status(204).send()) + .catch((err) => errorResponse(res, err)) + ); }