utils: Allow to remove values from sysrepo

Allow removing nodes in valuesPush so we can change and/or remove some
values from Sysrepo in one go.

The deletion of a node is implemented as NETCONF (RFC 6241 [1]) "remove"
operation. The remove operation deletes the data if present. If not, the
operation is a no-op.
If "delete" operation was used, the data would be removed (if present)
too. But if the data are not present it would result in an error [1].

We'd like to avoid the error without implementing a function that checks
if the data are present (see next commits).

[1] https://tools.ietf.org/html/rfc6241#page-38

Change-Id: Id4a0a83265f36a7bdac30a1c2d8247d25551d598
diff --git a/src/utils/sysrepo.cpp b/src/utils/sysrepo.cpp
index bd1b506..9716a6e 100644
--- a/src/utils/sysrepo.cpp
+++ b/src/utils/sysrepo.cpp
@@ -51,8 +51,34 @@
     sr_log_set_cb(spdlog_sr_log_cb);
 }
 
-void valuesToYang(const std::map<std::string, std::string>& values, std::shared_ptr<::sysrepo::Session> session, std::shared_ptr<libyang::Data_Node>& parent)
+void valuesToYang(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, std::shared_ptr<libyang::Data_Node>& parent)
 {
+    auto netconf = session->get_context()->get_module("ietf-netconf");
+
+    for (const auto& propertyName : removePaths) {
+        if (!parent) {
+            parent = std::make_shared<libyang::Data_Node>(
+                session->get_context(),
+                propertyName.c_str(),
+                nullptr,
+                LYD_ANYDATA_CONSTSTRING,
+                LYD_PATH_OPT_EDIT);
+        } else {
+            parent->new_path(
+                session->get_context(),
+                propertyName.c_str(),
+                nullptr,
+                LYD_ANYDATA_CONSTSTRING,
+                LYD_PATH_OPT_EDIT);
+        }
+
+        auto deletion = parent->find_path(propertyName.c_str());
+        if (deletion->number() != 1) {
+            throw std::logic_error {"Cannot find XPath " + propertyName + " for deletion in libyang's new_path() output"};
+        }
+        deletion->data()[0]->insert_attr(netconf, "operation", "remove");
+    }
+
     for (const auto& [propertyName, value] : values) {
         if (!parent) {
             parent = std::make_shared<libyang::Data_Node>(
@@ -72,23 +98,23 @@
     }
 }
 
-/** @brief Set values into Sysrepo's specified datastore. It changes the datastore and after the data are applied, the original datastore is restored. */
-void valuesPush(const std::map<std::string, std::string>& values, std::shared_ptr<::sysrepo::Session> session, sr_datastore_t datastore)
+/** @brief Set or remove values in Sysrepo's specified datastore. It changes the datastore and after the data are applied, the original datastore is restored. */
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, sr_datastore_t datastore)
 {
     sr_datastore_t oldDatastore = session->session_get_ds();
     session->session_switch_ds(datastore);
 
-    valuesPush(values, session);
+    valuesPush(values, removePaths, session);
 
     session->apply_changes();
     session->session_switch_ds(oldDatastore);
 }
 
-/** @brief Set values into Sysrepo's current datastore. */
-void valuesPush(const std::map<std::string, std::string>& values, std::shared_ptr<::sysrepo::Session> session)
+/** @brief Set or remove paths in Sysrepo's current datastore. */
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session)
 {
     libyang::S_Data_Node edit;
-    valuesToYang(values, session, edit);
+    valuesToYang(values, removePaths, session, edit);
 
     session->edit_batch(edit, "merge");
     session->apply_changes();