Merge "CI: re-enable ARM builds"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 39043a0..448b094 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,7 +49,7 @@
option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${DOXYGEN_FOUND})
find_package(docopt REQUIRED)
-find_package(Boost REQUIRED)
+find_package(Boost REQUIRED COMPONENTS filesystem)
find_library(REPLXX_LIBRARY NAMES replxx replxx-d REQUIRED)
find_path(REPLXX_PATH replxx.hxx)
if("${REPLXX_PATH}" STREQUAL REPLXX_PATH-NOTFOUND)
@@ -61,9 +61,6 @@
pkg_check_modules(SYSREPO REQUIRED sysrepo-cpp>=1.4.79 IMPORTED_TARGET sysrepo)
pkg_check_modules(LIBNETCONF2 REQUIRED libnetconf2>=1.1.32 IMPORTED_TARGET libnetconf2)
-# we don't need filename tracking, and we prefer to use header-only Boost
-add_definitions(-DBOOST_SPIRIT_X3_NO_FILESYSTEM)
-
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/)
add_library(ast_values STATIC
@@ -165,6 +162,19 @@
target_link_libraries(yang-cli stdc++fs)
endif()
+add_executable(netconf-cli
+ src/cli.cpp
+ src/cli-netconf.cpp
+ )
+target_compile_definitions(netconf-cli PRIVATE NETCONF_CLI)
+
+# Boost.Process needs linking with threads, but doesn't have special CMake target which would do that for us. So, we
+# need to link manually. This will hopefully change in the future.
+# https://discourse.cmake.org/t/boost-process-target-doesnt-exist-for-thread-linking/2113
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+find_package(Threads)
+target_link_libraries(netconf-cli netconfaccess Threads::Threads Boost::filesystem)
+cli_link_required(netconf-cli)
include(CTest)
@@ -372,6 +382,7 @@
endif()
install(TARGETS
+ netconf-cli
sysrepo-cli
yang-cli
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/)
diff --git a/src/cli-netconf.cpp b/src/cli-netconf.cpp
new file mode 100644
index 0000000..4f9275a
--- /dev/null
+++ b/src/cli-netconf.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+
+#include <boost/fusion/adapted.hpp>
+#include <boost/spirit/home/x3.hpp>
+#include <optional>
+#include <stdexcept>
+#include <unistd.h>
+#include "cli-netconf.hpp"
+
+SshProcess sshProcess(const std::string& target, const std::string& port)
+{
+ namespace bp = boost::process;
+ bp::pipe in;
+ bp::pipe out;
+ auto sshPath = bp::search_path("ssh");
+ if (sshPath.empty()) {
+ throw std::runtime_error("ssh not found in PATH.");
+ }
+ if (target.front() == '@') {
+ throw std::runtime_error("Invalid username.");
+ }
+ bp::child ssh(sshPath,
+ target,
+ "-p",
+ port,
+ "-s",
+ "netconf",
+ bp::std_out > out, bp::std_in < in);
+
+ return {std::move(ssh), std::move(in), std::move(out)};
+}
diff --git a/src/cli-netconf.hpp b/src/cli-netconf.hpp
new file mode 100644
index 0000000..4849531
--- /dev/null
+++ b/src/cli-netconf.hpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+#include <boost/process.hpp>
+#include <string>
+
+struct SshProcess {
+ boost::process::child process;
+ boost::process::pipe std_in;
+ boost::process::pipe std_out;
+};
+SshProcess sshProcess(const std::string& target, const std::string& port);
diff --git a/src/cli.cpp b/src/cli.cpp
index 68d4a27..f89c8d6 100644
--- a/src/cli.cpp
+++ b/src/cli.cpp
@@ -13,9 +13,9 @@
#include "NETCONF_CLI_VERSION.h"
#include "interpreter.hpp"
#include "proxy_datastore.hpp"
+#include "yang_schema.hpp"
#if defined(SYSREPO_CLI)
#include "sysrepo_access.hpp"
-#include "yang_schema.hpp"
#define PROGRAM_NAME "sysrepo-cli"
static const auto usage = R"(CLI interface to sysrepo
@@ -47,6 +47,22 @@
-e <enable_features> Feature to enable after modules are loaded. This option can be supplied more than once. Format: <module_name>:<feature>
-i <data_file> File to import data from
--configonly Disable editing of operational data)";
+#elif defined(NETCONF_CLI)
+// FIXME: improve usage
+static const auto usage = R"(CLI interface for NETCONF
+
+Usage:
+ netconf-cli [-v] [-p <port>] <host>
+ netconf-cli (-h | --help)
+ netconf-cli --version
+
+Options:
+ -v enable verbose mode
+ -p <port> port number [default: 830]
+)";
+#include "netconf_access.hpp"
+#include "cli-netconf.hpp"
+#define PROGRAM_NAME "netconf-access"
#else
#error "Unknown CLI backend"
#endif
@@ -121,11 +137,27 @@
datastore->addDataFile(dataFile);
}
}
+#elif defined(NETCONF_CLI)
+ auto verbose = args.at("-v").asBool();
+ if (verbose) {
+ NetconfAccess::setNcLogLevel(NC_VERB_DEBUG);
+ }
+
+ SshProcess process;
+ std::shared_ptr<NetconfAccess> datastore;
+
+ try {
+ process = sshProcess(args.at("<host>").asString(), args.at("-p").asString());
+ datastore = std::make_shared<NetconfAccess>(process.std_out.native_source(), process.std_in.native_sink());
+ } catch (std::runtime_error& ex) {
+ std::cerr << "SSH connection failed: " << ex.what() << "\n";
+ return 1;
+ }
#else
#error "Unknown CLI backend"
#endif
-#if defined(SYSREPO_CLI)
+#if defined(SYSREPO_CLI) || defined(NETCONF_CLI)
auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>& datastore) {
return std::make_shared<YangAccess>(std::static_pointer_cast<YangSchema>(datastore->schema()));
};
diff --git a/src/netconf-client.cpp b/src/netconf-client.cpp
index 1ff83de..42e9edc 100644
--- a/src/netconf-client.cpp
+++ b/src/netconf-client.cpp
@@ -20,6 +20,14 @@
namespace impl {
+static client::LogCb logCallback;
+
+static void logViaCallback(NC_VERB_LEVEL level, const char* message)
+{
+ logCallback(level, message);
+}
+
+
/** @short Initialization of the libnetconf2 library client
Just a safe wrapper over nc_client_init and nc_client_destroy, really.
@@ -28,7 +36,6 @@
ClientInit()
{
nc_client_init();
- nc_verbosity(NC_VERB_DEBUG);
}
~ClientInit()
@@ -178,6 +185,17 @@
namespace client {
+void setLogLevel(NC_VERB_LEVEL level)
+{
+ nc_verbosity(level);
+}
+
+void setLogCallback(const client::LogCb& callback)
+{
+ impl::logCallback = callback;
+ nc_set_print_clb(impl::logViaCallback);
+}
+
struct nc_session* Session::session_internal()
{
return m_session;
@@ -238,6 +256,17 @@
return session;
}
+std::unique_ptr<Session> Session::connectFd(const int source, const int sink)
+{
+ impl::ClientInit::instance();
+
+ auto session = std::make_unique<Session>(nc_connect_inout(source, sink, nullptr));
+ if (!session->m_session) {
+ throw std::runtime_error{"nc_connect_inout failed"};
+ }
+ return session;
+}
+
std::unique_ptr<Session> Session::connectSocket(const std::string& path)
{
impl::ClientInit::instance();
diff --git a/src/netconf-client.hpp b/src/netconf-client.hpp
index db004e3..6bf4169 100644
--- a/src/netconf-client.hpp
+++ b/src/netconf-client.hpp
@@ -1,6 +1,7 @@
#pragma once
#include <functional>
+#include <libnetconf2/log.h>
#include <libnetconf2/messages_client.h>
#include <memory>
#include <optional>
@@ -25,6 +26,10 @@
};
using KbdInteractiveCb = std::function<std::string(const std::string&, const std::string&, const std::string&, bool)>;
+using LogCb = std::function<void(NC_VERB_LEVEL, const char*)>;
+
+void setLogLevel(NC_VERB_LEVEL level);
+void setLogCallback(const LogCb& callback);
class Session {
public:
@@ -33,6 +38,7 @@
static std::unique_ptr<Session> connectPubkey(const std::string& host, const uint16_t port, const std::string& user, const std::string& pubPath, const std::string& privPath);
static std::unique_ptr<Session> connectKbdInteractive(const std::string& host, const uint16_t port, const std::string& user, const KbdInteractiveCb& callback);
static std::unique_ptr<Session> connectSocket(const std::string& path);
+ static std::unique_ptr<Session> connectFd(const int source, const int sink);
[[nodiscard]] std::vector<std::string_view> capabilities() const;
std::shared_ptr<libyang::Data_Node> getConfig(const NC_DATASTORE datastore,
const std::optional<const std::string> filter = std::nullopt); // TODO: arguments...
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 4b151d3..307ccf1 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -33,6 +33,12 @@
{
}
+NetconfAccess::NetconfAccess(const int source, const int sink)
+ : m_session(libnetconf::client::Session::connectFd(source, sink))
+ , m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
+{
+}
+
NetconfAccess::NetconfAccess(std::unique_ptr<libnetconf::client::Session>&& session)
: m_session(std::move(session))
, m_schema(std::make_shared<YangSchema>(m_session->libyangContext()))
@@ -45,6 +51,16 @@
{
}
+void NetconfAccess::setNcLogLevel(NC_VERB_LEVEL level)
+{
+ libnetconf::client::setLogLevel(level);
+}
+
+void NetconfAccess::setNcLogCallback(const LogCb& callback)
+{
+ libnetconf::client::setLogCallback(callback);
+}
+
void NetconfAccess::setLeaf(const std::string& path, leaf_data_ value)
{
auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index d6100a7..45973a6 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -7,6 +7,7 @@
#pragma once
+#include <libnetconf2/log.h>
#include <string>
#include "datastore_access.hpp"
@@ -27,13 +28,19 @@
class Schema;
class YangSchema;
+using LogCb = std::function<void(NC_VERB_LEVEL, const char*)>;
+
class NetconfAccess : public DatastoreAccess {
public:
NetconfAccess(const std::string& hostname, uint16_t port, const std::string& user, const std::string& pubKey, const std::string& privKey);
NetconfAccess(const std::string& socketPath);
+ NetconfAccess(const int source, const int sink);
NetconfAccess(std::unique_ptr<libnetconf::client::Session>&& session);
~NetconfAccess() override;
[[nodiscard]] Tree getItems(const std::string& path) const override;
+
+ static void setNcLogLevel(NC_VERB_LEVEL level);
+ static void setNcLogCallback(const LogCb& callback);
void setLeaf(const std::string& path, leaf_data_ value) override;
void createItem(const std::string& path) override;
void deleteItem(const std::string& path) override;