Loading Builder/include/Builder.h +2 −109 Original line number Diff line number Diff line #pragma once #include "Messenger.h" #include "Logger.h" #include "Messenger.h" #include <boost/beast/core.hpp> #include <boost/beast/websocket.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/spawn.hpp> #include <boost/asio/buffer.hpp> #include <boost/process.hpp> #include <boost/regex.hpp> #include <boost/filesystem.hpp> #include <boost/asio.hpp> namespace asio = boost::asio; using asio::ip::tcp; namespace bp = boost::process; class Builder { public: explicit Builder() { // Full build connection - this will not run until the io_service is started asio::spawn(io_service, [&](asio::yield_context yield) { boost::system::error_code error; Messenger client(io_service, "8080", yield, error); if(error) { throw std::runtime_error("Error connecting to client: " + error.message()); } // Receive client data ClientData client_data = client.async_read_client_data(yield, error); if (error) { throw std::runtime_error("Error receiving client data: " + error.message()); } // Receive the definition file from the client client.async_read_file("container.def", yield, error); if (error) { throw std::runtime_error("Error receiving definition file: " + error.message()); } logger::write("Received container.def"); if(client_data.arch == Architecture::ppc64le) { // A dirty hack but the ppc64le qemu executable must be in the place the kernel expects it // Modify the definition to copy this executable in during %setup boost::filesystem::ofstream def{"container.def", std::ios::app}; std::string copy_qemu("\n%setup\ncp /usr/bin/qemu-ppc64le ${SINGULARITY_ROOTFS}/usr/bin/qemu-ppc64le"); def << copy_qemu; } // Create a pipe to communicate with our build subprocess bp::async_pipe std_pipe(io_service); // Launch our build as a subprocess // We use "unbuffer" to fake the build into thinking it has a real TTY, which the command output eventually will // This causes things like wget and color ls to work nicely std::string build_command("/usr/bin/sudo "); // If the cleint has a tty trick the subprocess into thinking that as well if(client_data.tty) { build_command += "/usr/bin/unbuffer "; } build_command += "/usr/local/bin/singularity build ./container.img ./container.def"; bp::group group; std::error_code build_ec; bp::child build_child(build_command, bp::std_in.close(), (bp::std_out & bp::std_err) > std_pipe, group, build_ec); if (build_ec) { throw std::runtime_error("subprocess error: " + build_ec.message()); } logger::write("launched build process: " + build_command); // stream from the async pipe process output to the socket std::array<char, 4096> buffer; std::size_t read_size = 0; boost::system::error_code stream_error; bool fin = false; do { // Read from the pipe into a buffer auto read_bytes = std_pipe.async_read_some(buffer, yield[stream_error]); if (stream_error != boost::system::errc::success && stream_error != boost::asio::error::eof) { throw std::runtime_error("reading process pipe failed: " + stream_error.message()); } // Wrap it up if we've hit EOF if(stream_error == boost::asio::error::eof) { fin = true; } // Write the buffer to our socket client.async_write_some(fin, asio::buffer(buffer, read_bytes), yield, error); if (error) { throw std::runtime_error("sending process pipe failed: " + error.message()); } } while (!fin); // Get the return value from the build subprocess logger::write("Waiting on build process to exit"); build_child.wait(); int build_code = build_child.exit_code(); // Send the container to the client if (build_code == 0) { logger::write("Build complete, sending container"); client.async_write_file("container.img", yield, error); if (error) { throw std::runtime_error("Sending file to client failed: " + error.message()); } } else { throw std::runtime_error("Build failed, not sending container"); } }); } explicit Builder(); // Start the IO service void run(); Loading Builder/src/Builder.cpp +82 −0 Original line number Diff line number Diff line #include "Builder.h" #include "Logger.h" #include "Messenger.h" Builder::Builder() { // Full build connection - this will not run until the io_service is started asio::spawn(io_service, [&](asio::yield_context yield) { boost::system::error_code error; Messenger client(io_service, "8080", yield, error); if (error) { throw std::runtime_error("Error connecting to client: " + error.message()); } // Receive client data ClientData client_data = client.async_read_client_data(yield, error); if (error) { throw std::runtime_error("Error receiving client data: " + error.message()); } // Receive the definition file from the client client.async_read_file("container.def", yield, error); if (error) { throw std::runtime_error("Error receiving definition file: " + error.message()); } logger::write("Received container.def"); if (client_data.arch == Architecture::ppc64le) { // A dirty hack but the ppc64le qemu executable must be in the place the kernel expects it // Modify the definition to copy this executable in during %setup // NOTE: singularity currently doesn't have a way to inject this file in before bootstrap // and so DockerHub or local singularity files are the only supported ppc64le bootstrapper boost::filesystem::ofstream def{"container.def", std::ios::app}; std::string copy_qemu( "\n%setup\ncp /usr/bin/qemu-ppc64le ${SINGULARITY_ROOTFS}/usr/bin/qemu-ppc64le"); def << copy_qemu; } // Create a pipe to communicate with our build subprocess bp::async_pipe std_pipe(io_service); // Launch our build as a subprocess // We use "unbuffer" to fake the build into thinking it has a real TTY, which the command output eventually will // This causes things like wget and color ls to work nicely std::string build_command("/usr/bin/sudo "); // If the cleint has a tty trick the subprocess into thinking that as well if (client_data.tty) { build_command += "/usr/bin/unbuffer "; } build_command += "/usr/local/bin/singularity build ./container.img ./container.def"; bp::group group; std::error_code build_ec; bp::child build_child(build_command, bp::std_in.close(), (bp::std_out & bp::std_err) > std_pipe, group, build_ec); if (build_ec) { throw std::runtime_error("subprocess error: " + build_ec.message()); } logger::write("launched build process: " + build_command); // stream from the async pipe process output to the socket client.async_write_pipe(std_pipe, yield, error); // Get the return value from the build subprocess logger::write("Waiting on build process to exit"); build_child.wait(); int build_code = build_child.exit_code(); // Send the container to the client if (build_code == 0) { logger::write("Build complete, sending container"); client.async_write_file("container.img", yield, error); if (error) { throw std::runtime_error("Sending file to client failed: " + error.message()); } } else { throw std::runtime_error("Build failed, not sending container"); } }); } void Builder::run() { io_service.run(); Loading Builder/src/main.cpp +1 −0 Original line number Diff line number Diff line #include "Builder.h" #include "Logger.h" int main(int argc, char *argv[]) { Loading Common/include/Messenger.h +8 −6 Original line number Diff line number Diff line Loading @@ -9,17 +9,15 @@ #include <boost/beast/core.hpp> #include <boost/beast/websocket.hpp> #include <iostream> //#include <boost/archive/text_iarchive.hpp> //#include <boost/archive/text_oarchive.hpp> #include "BuilderData.h" //#include <boost/progress.hpp> //#include <boost/crc.hpp> #include <memory.h> #include "Logger.h" #include "ClientData.h" #include <boost/process.hpp> namespace asio = boost::asio; using asio::ip::tcp; namespace bp = boost::process; namespace beast = boost::beast; namespace websocket = beast::websocket; Loading Loading @@ -79,8 +77,7 @@ public: void async_write_file(boost::filesystem::path file_path, asio::yield_context yield, boost::system::error_code& error, ); boost::system::error_code& error); BuilderData async_read_builder(asio::yield_context yield, boost::system::error_code& error); Loading @@ -96,6 +93,11 @@ public: asio::yield_context yield, boost::system::error_code& error); void async_write_pipe(bp::async_pipe& pipe, asio::yield_context yield, boost::system::error_code& error); private: websocket::stream<tcp::socket> stream; }; No newline at end of file Common/src/Messenger.cpp +25 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ #include <boost/archive/text_oarchive.hpp> #include "Builder.h" namespace asio = boost::asio; using asio::ip::tcp; Loading Loading @@ -221,3 +222,27 @@ void Messenger::async_write_client_data(ClientData client_data) { return; } } void Messenger::async_write_pipe(bp::async_pipe pipe, asio::yield_context yield, boost::system::error_code &error) { std::array<char, 4096> buffer; boost::system::error_code stream_error; bool fin = false; do { // Read from the pipe into a buffer auto read_bytes = pipe.async_read_some(buffer, yield[stream_error]); if (stream_error != boost::system::errc::success && stream_error != boost::asio::error::eof) { throw std::runtime_error("reading process pipe failed: " + stream_error.message()); } // Wrap it up if we've hit EOF on our process output if (stream_error == boost::asio::error::eof) { fin = true; } // Write the buffer to our socket stream.async_write_some(fin, asio::buffer(buffer.data(), read_bytes), yield, error); if (error) { throw std::runtime_error("sending process pipe failed: " + error.message()); } } while (!fin); } No newline at end of file Loading
Builder/include/Builder.h +2 −109 Original line number Diff line number Diff line #pragma once #include "Messenger.h" #include "Logger.h" #include "Messenger.h" #include <boost/beast/core.hpp> #include <boost/beast/websocket.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/spawn.hpp> #include <boost/asio/buffer.hpp> #include <boost/process.hpp> #include <boost/regex.hpp> #include <boost/filesystem.hpp> #include <boost/asio.hpp> namespace asio = boost::asio; using asio::ip::tcp; namespace bp = boost::process; class Builder { public: explicit Builder() { // Full build connection - this will not run until the io_service is started asio::spawn(io_service, [&](asio::yield_context yield) { boost::system::error_code error; Messenger client(io_service, "8080", yield, error); if(error) { throw std::runtime_error("Error connecting to client: " + error.message()); } // Receive client data ClientData client_data = client.async_read_client_data(yield, error); if (error) { throw std::runtime_error("Error receiving client data: " + error.message()); } // Receive the definition file from the client client.async_read_file("container.def", yield, error); if (error) { throw std::runtime_error("Error receiving definition file: " + error.message()); } logger::write("Received container.def"); if(client_data.arch == Architecture::ppc64le) { // A dirty hack but the ppc64le qemu executable must be in the place the kernel expects it // Modify the definition to copy this executable in during %setup boost::filesystem::ofstream def{"container.def", std::ios::app}; std::string copy_qemu("\n%setup\ncp /usr/bin/qemu-ppc64le ${SINGULARITY_ROOTFS}/usr/bin/qemu-ppc64le"); def << copy_qemu; } // Create a pipe to communicate with our build subprocess bp::async_pipe std_pipe(io_service); // Launch our build as a subprocess // We use "unbuffer" to fake the build into thinking it has a real TTY, which the command output eventually will // This causes things like wget and color ls to work nicely std::string build_command("/usr/bin/sudo "); // If the cleint has a tty trick the subprocess into thinking that as well if(client_data.tty) { build_command += "/usr/bin/unbuffer "; } build_command += "/usr/local/bin/singularity build ./container.img ./container.def"; bp::group group; std::error_code build_ec; bp::child build_child(build_command, bp::std_in.close(), (bp::std_out & bp::std_err) > std_pipe, group, build_ec); if (build_ec) { throw std::runtime_error("subprocess error: " + build_ec.message()); } logger::write("launched build process: " + build_command); // stream from the async pipe process output to the socket std::array<char, 4096> buffer; std::size_t read_size = 0; boost::system::error_code stream_error; bool fin = false; do { // Read from the pipe into a buffer auto read_bytes = std_pipe.async_read_some(buffer, yield[stream_error]); if (stream_error != boost::system::errc::success && stream_error != boost::asio::error::eof) { throw std::runtime_error("reading process pipe failed: " + stream_error.message()); } // Wrap it up if we've hit EOF if(stream_error == boost::asio::error::eof) { fin = true; } // Write the buffer to our socket client.async_write_some(fin, asio::buffer(buffer, read_bytes), yield, error); if (error) { throw std::runtime_error("sending process pipe failed: " + error.message()); } } while (!fin); // Get the return value from the build subprocess logger::write("Waiting on build process to exit"); build_child.wait(); int build_code = build_child.exit_code(); // Send the container to the client if (build_code == 0) { logger::write("Build complete, sending container"); client.async_write_file("container.img", yield, error); if (error) { throw std::runtime_error("Sending file to client failed: " + error.message()); } } else { throw std::runtime_error("Build failed, not sending container"); } }); } explicit Builder(); // Start the IO service void run(); Loading
Builder/src/Builder.cpp +82 −0 Original line number Diff line number Diff line #include "Builder.h" #include "Logger.h" #include "Messenger.h" Builder::Builder() { // Full build connection - this will not run until the io_service is started asio::spawn(io_service, [&](asio::yield_context yield) { boost::system::error_code error; Messenger client(io_service, "8080", yield, error); if (error) { throw std::runtime_error("Error connecting to client: " + error.message()); } // Receive client data ClientData client_data = client.async_read_client_data(yield, error); if (error) { throw std::runtime_error("Error receiving client data: " + error.message()); } // Receive the definition file from the client client.async_read_file("container.def", yield, error); if (error) { throw std::runtime_error("Error receiving definition file: " + error.message()); } logger::write("Received container.def"); if (client_data.arch == Architecture::ppc64le) { // A dirty hack but the ppc64le qemu executable must be in the place the kernel expects it // Modify the definition to copy this executable in during %setup // NOTE: singularity currently doesn't have a way to inject this file in before bootstrap // and so DockerHub or local singularity files are the only supported ppc64le bootstrapper boost::filesystem::ofstream def{"container.def", std::ios::app}; std::string copy_qemu( "\n%setup\ncp /usr/bin/qemu-ppc64le ${SINGULARITY_ROOTFS}/usr/bin/qemu-ppc64le"); def << copy_qemu; } // Create a pipe to communicate with our build subprocess bp::async_pipe std_pipe(io_service); // Launch our build as a subprocess // We use "unbuffer" to fake the build into thinking it has a real TTY, which the command output eventually will // This causes things like wget and color ls to work nicely std::string build_command("/usr/bin/sudo "); // If the cleint has a tty trick the subprocess into thinking that as well if (client_data.tty) { build_command += "/usr/bin/unbuffer "; } build_command += "/usr/local/bin/singularity build ./container.img ./container.def"; bp::group group; std::error_code build_ec; bp::child build_child(build_command, bp::std_in.close(), (bp::std_out & bp::std_err) > std_pipe, group, build_ec); if (build_ec) { throw std::runtime_error("subprocess error: " + build_ec.message()); } logger::write("launched build process: " + build_command); // stream from the async pipe process output to the socket client.async_write_pipe(std_pipe, yield, error); // Get the return value from the build subprocess logger::write("Waiting on build process to exit"); build_child.wait(); int build_code = build_child.exit_code(); // Send the container to the client if (build_code == 0) { logger::write("Build complete, sending container"); client.async_write_file("container.img", yield, error); if (error) { throw std::runtime_error("Sending file to client failed: " + error.message()); } } else { throw std::runtime_error("Build failed, not sending container"); } }); } void Builder::run() { io_service.run(); Loading
Builder/src/main.cpp +1 −0 Original line number Diff line number Diff line #include "Builder.h" #include "Logger.h" int main(int argc, char *argv[]) { Loading
Common/include/Messenger.h +8 −6 Original line number Diff line number Diff line Loading @@ -9,17 +9,15 @@ #include <boost/beast/core.hpp> #include <boost/beast/websocket.hpp> #include <iostream> //#include <boost/archive/text_iarchive.hpp> //#include <boost/archive/text_oarchive.hpp> #include "BuilderData.h" //#include <boost/progress.hpp> //#include <boost/crc.hpp> #include <memory.h> #include "Logger.h" #include "ClientData.h" #include <boost/process.hpp> namespace asio = boost::asio; using asio::ip::tcp; namespace bp = boost::process; namespace beast = boost::beast; namespace websocket = beast::websocket; Loading Loading @@ -79,8 +77,7 @@ public: void async_write_file(boost::filesystem::path file_path, asio::yield_context yield, boost::system::error_code& error, ); boost::system::error_code& error); BuilderData async_read_builder(asio::yield_context yield, boost::system::error_code& error); Loading @@ -96,6 +93,11 @@ public: asio::yield_context yield, boost::system::error_code& error); void async_write_pipe(bp::async_pipe& pipe, asio::yield_context yield, boost::system::error_code& error); private: websocket::stream<tcp::socket> stream; }; No newline at end of file
Common/src/Messenger.cpp +25 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ #include <boost/archive/text_oarchive.hpp> #include "Builder.h" namespace asio = boost::asio; using asio::ip::tcp; Loading Loading @@ -221,3 +222,27 @@ void Messenger::async_write_client_data(ClientData client_data) { return; } } void Messenger::async_write_pipe(bp::async_pipe pipe, asio::yield_context yield, boost::system::error_code &error) { std::array<char, 4096> buffer; boost::system::error_code stream_error; bool fin = false; do { // Read from the pipe into a buffer auto read_bytes = pipe.async_read_some(buffer, yield[stream_error]); if (stream_error != boost::system::errc::success && stream_error != boost::asio::error::eof) { throw std::runtime_error("reading process pipe failed: " + stream_error.message()); } // Wrap it up if we've hit EOF on our process output if (stream_error == boost::asio::error::eof) { fin = true; } // Write the buffer to our socket stream.async_write_some(fin, asio::buffer(buffer.data(), read_bytes), yield, error); if (error) { throw std::runtime_error("sending process pipe failed: " + error.message()); } } while (!fin); } No newline at end of file