Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions include/docker_client.hh
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
#include "create_container_cmd.hh"
#include "events_cmd.hh"
#include "info_cmd.hh"
#include "inspect_container_cmd.hh"
#include "inspect_image_cmd.hh"
#include "load_image_cmd.hh"
#include "ping_cmd.hh"
#include "pull_image_cmd.hh"
#include "remove_image_cmd.hh"
#include "remove_container_cmd.hh"
#include "remove_image_cmd.hh"
#include "start_container_cmd.hh"
#include "stop_container_cmd.hh"
#include "inspect_container_cmd.hh"
#include "inspect_image_cmd.hh"
#include "version_cmd.hh"

namespace dockercpp {
Expand All @@ -36,13 +37,16 @@ class DockerClient {

std::shared_ptr<command::PullImageCmd> pullImageCmd(std::string repository);

std::shared_ptr<command::LoadImageCmd> loadImageCmd(std::string tarContents);

std::shared_ptr<command::InfoCmd> infoCmd();

std::shared_ptr<command::RemoveImageCmd> removeImageCmd(std::string image);

std::shared_ptr<command::InspectImageCmd> inspectImageCmd(std::string image);

std::shared_ptr<command::RemoveContainerCmd> removeContainerCmd(std::string id);
std::shared_ptr<command::RemoveContainerCmd> removeContainerCmd(
std::string id);

std::shared_ptr<command::EventsCmd> eventsCmd();
};
Expand Down
48 changes: 48 additions & 0 deletions include/load_image_cmd.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef INCLUDE_LOAD_IMAGE_CMD_HPP
#define INCLUDE_LOAD_IMAGE_CMD_HPP

#include <memory>
#include <string>

#include "abstr_sync_docker_cmd_exec.hh"
#include "synch_docker_cmd.hh"

namespace dockercpp::command {

class LoadImageCmd : public SynchDockerCmd<std::string>,
public std::enable_shared_from_this<LoadImageCmd> {
public:
explicit LoadImageCmd(const std::string& tarContents);

std::string getTarContents();

~LoadImageCmd() {}

private:
std::string m_tar;
};

namespace load {
class Exec : public exec::DockerCmdSyncExec<LoadImageCmd, std::string> {
public:
~Exec() {}
};
} // namespace load

class LoadImageCmdImpl : public LoadImageCmd,
public AbstrDockerCmd<LoadImageCmd, std::string> {
public:
LoadImageCmdImpl(std::unique_ptr<load::Exec> exec, const std::string& tar);

std::string exec() override;

void close() override;
~LoadImageCmdImpl();

private:
std::string m_tar;
};

} // namespace dockercpp::command

#endif /* INCLUDE_LOAD_IMAGE_CMD_HPP */
1 change: 1 addition & 0 deletions include/webtarget.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class InvocationBuilder {
std::string get();

std::string post(std::string &json);
std::pair<std::string, long> post_with_code(std::string &body);

bool deletehttp();

Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ add_library(docker_cpp_client
stats_container_cmd.cc
update_container_cmd.cc
webtarget.cc
load_image_cmd.cc
load_image_cmd_exec.cc
abstr_sync_docker_cmd_exec.cc
remove_container_cmd.cc
remove_container_cmd_exec.cc
Expand Down
12 changes: 10 additions & 2 deletions src/curl_docker_http_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,20 @@ http::Response postcurl(Request &request) {
}

spdlog::info("About to send post: {}", request.body().c_str());
// Ensure binary-safe upload for arbitrary body contents (e.g., tar archives)
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE,
static_cast<curl_off_t>(fields.size()));

curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: application/json");
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "charset: utf-8");
// If posting a tar archive for image load, use application/x-tar
if (request.path().find("/images/load") != std::string::npos) {
headers = curl_slist_append(headers, "Content-Type: application/x-tar");
} else {
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "charset: utf-8");
}

std::string response_string;
std::string header_string;
Expand Down
8 changes: 8 additions & 0 deletions src/docker_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "version_cmd.hh"
#include "version_cmd_exec.hh"
#include "events_cmd_exec.hh"
#include "load_image_cmd.hh"
#include "load_image_cmd_exec.hh"

namespace dockercpp {

Expand Down Expand Up @@ -69,6 +71,12 @@ std::shared_ptr<command::PullImageCmd> DockerClient::pullImageCmd(
repository);
}

std::shared_ptr<command::LoadImageCmd> DockerClient::loadImageCmd(
std::string tarContents) {
return std::make_shared<command::LoadImageCmdImpl>(
std::move(std::make_unique<command::exec::LoadImageCmdExec>()), tarContents);
}

std::shared_ptr<command::InfoCmd> DockerClient::infoCmd() {
return std::make_shared<command::InfoCmdImpl>(
std::move(std::make_unique<command::exec::InfoCmdExec>()));
Expand Down
19 changes: 19 additions & 0 deletions src/load_image_cmd.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "load_image_cmd.hh"

namespace dockercpp::command {

LoadImageCmd::LoadImageCmd(const std::string& tarContents) : m_tar(tarContents) {}

std::string LoadImageCmd::getTarContents() { return m_tar; }

LoadImageCmdImpl::LoadImageCmdImpl(std::unique_ptr<load::Exec> exec,
const std::string& tar)
: AbstrDockerCmd<LoadImageCmd, std::string>(std::move(exec)), LoadImageCmd(tar), m_tar(tar) {}

std::string LoadImageCmdImpl::exec() { return m_execution->exec(shared_from_this()); }

void LoadImageCmdImpl::close() {}

LoadImageCmdImpl::~LoadImageCmdImpl() {}

} // namespace dockercpp::command
35 changes: 35 additions & 0 deletions src/load_image_cmd_exec.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "load_image_cmd_exec.hh"

#include <spdlog/spdlog.h>

#include "abstr_sync_docker_cmd_exec.hh"
#include "docker_exception.hh"
#include "webtarget.hh"

namespace dockercpp::command::exec {

LoadImageCmdExec::LoadImageCmdExec()
: AbstrSyncDockerCmdExec<dockercpp::command::LoadImageCmd, std::string>(),
load::Exec() {}

std::string LoadImageCmdExec::exec(
std::shared_ptr<dockercpp::command::LoadImageCmd> command) {
return execute(command);
}

std::string LoadImageCmdExec::execute(
std::shared_ptr<dockercpp::command::LoadImageCmd> command) {
core::WebTarget webResource = m_webTarget->path("/images/load");

auto body = command->getTarContents();

auto [response, statusCode] = webResource.request().post_with_code(body);

if (statusCode != 200) {
throw dockercpp::DockerException("Error loading image", statusCode, response);
}

return response;
}

} // namespace dockercpp::command::exec
23 changes: 23 additions & 0 deletions src/load_image_cmd_exec.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef LOAD_IMAGE_CMD_EXEC_HH
#define LOAD_IMAGE_CMD_EXEC_HH

#include "abstr_sync_docker_cmd_exec.hh"
#include "load_image_cmd.hh"

namespace dockercpp::command::exec {

class LoadImageCmdExec
: public AbstrSyncDockerCmdExec<dockercpp::command::LoadImageCmd, std::string>,
public load::Exec {
public:
LoadImageCmdExec();

std::string exec(std::shared_ptr<dockercpp::command::LoadImageCmd> command) override;

std::string execute(std::shared_ptr<dockercpp::command::LoadImageCmd> command) override;
~LoadImageCmdExec() {}
};

} // namespace dockercpp::command::exec

#endif
18 changes: 18 additions & 0 deletions src/webtarget.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ std::string InvocationBuilder::post(std::string& json) {
return client.execute(request).getBody();
}

std::pair<std::string, long> InvocationBuilder::post_with_code(std::string& body) {
spdlog::info("post with code requested");
transport::http::Request request =
transport::http::Request::make()
.withMethod(transport::http::Request::Method::POST)
.withBody(body)
.withPath(m_path);

dockercpp::transport::http::CurlDockerHttpClient client =
dockercpp::transport::http::CurlDockerHttpClient::make()
.withDockerHost("")
.withConnectTimeout(10)
.withReadTimeout(10);

auto response = client.execute(request);
return {response.getBody(), response.getStatusCode()};
}

bool InvocationBuilder::deletehttp() {
auto [response, code] = deletehttp_with_code();
return code == 200;
Expand Down
3 changes: 2 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
Expand All @@ -22,6 +22,7 @@ add_executable(
update_container_cmd_test.cc
inspect_container_response_test.cc
remove_image_cmd_exec_test.cc
load_image_cmd_test.cc
version_test.cc
)

Expand Down
68 changes: 68 additions & 0 deletions tests/load_image_cmd_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <gtest/gtest.h>
#include <memory>
#include <string>
#include <fstream>
#include <streambuf>
#include <chrono>
#include <thread>

#include "docker_client.hh"
#include "inspect_image_cmd.hh"

namespace dockercpp::command::test {

class LoadImageCmdIT : public ::testing::Test {
protected:
void SetUp() override { dockerClient = std::make_unique<DockerClient>(); }

void TearDown() override { dockerClient.reset(); }

std::unique_ptr<DockerClient> dockerClient;
};

TEST_F(LoadImageCmdIT, loadImageFromTar) {
// Build a tiny image, save it to tar, remove it, then load via API
const std::string tmpdir = "/tmp/docker_cpp_load_test";
system((std::string("rm -rf ") + tmpdir).c_str());
system((std::string("mkdir -p ") + tmpdir).c_str());

// Write a minimal Dockerfile
std::ofstream df(tmpdir + "/Dockerfile");
df << "FROM busybox\nCMD [\"/bin/sh\"]\n";
df.close();

// Build image
int rc = system((std::string("docker build -t docker-cpp/load:1.0 ") + tmpdir + " > /dev/null").c_str());
ASSERT_EQ(rc, 0);

// Inspect to get image id
auto info = dockerClient->inspectImageCmd("docker-cpp/load:1.0")->exec();
ASSERT_FALSE(info.id.empty());
std::string imageId = info.id;

// Save to tar
std::string tarPath = "/tmp/docker_cpp_load_image.tar";
rc = system((std::string("docker save docker-cpp/load:1.0 -o ") + tarPath).c_str());
ASSERT_EQ(rc, 0);

// Remove image
ASSERT_NO_THROW(dockerClient->removeImageCmd("docker-cpp/load:1.0")->withForce(true).exec());

// Read tar into string
std::ifstream ifs(tarPath, std::ios::binary);
ASSERT_TRUE(ifs.good());
std::string tarContents((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ifs.close();

// Load via our API
ASSERT_NO_THROW(dockerClient->loadImageCmd(tarContents)->exec());

// Allow Docker to register the image
std::this_thread::sleep_for(std::chrono::seconds(3));

// Verify image is present
auto info2 = dockerClient->inspectImageCmd("docker-cpp/load:1.0")->exec();
EXPECT_FALSE(info2.id.empty());
}

} // namespace dockercpp::command::test
Loading