Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1189 from doanac/docker-appmgr-2
Browse files Browse the repository at this point in the history
RFC: Add a docker-app package manager
  • Loading branch information
pattivacek authored May 31, 2019
2 parents 59b04d0 + 3879216 commit 06ca003
Show file tree
Hide file tree
Showing 15 changed files with 414 additions and 13 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ option(PEDANTIC_WARNINGS "Compile with pedantic warnings" OFF)
option(BUILD_WITH_CODE_COVERAGE "Enable gcov code coverage" OFF)
option(BUILD_OSTREE "Set to ON to compile with ostree support" OFF)
option(BUILD_DEB "Set to ON to compile with debian packages support" OFF)
option(BUILD_DOCKERAPP "Set to ON to compile with package manager support of docker-app" OFF)
option(BUILD_P11 "Support for key storage in a HSM via PKCS#11" OFF)
option(BUILD_SOTA_TOOLS "Set to ON to build SOTA tools" OFF)
option(BUILD_PARTIAL "Set to ON to compile with partial secondaries support" OFF)
Expand Down Expand Up @@ -122,6 +123,10 @@ else(BUILD_DEB)
unset(LIBDPKG_LIBRARIES CACHE)
endif(BUILD_DEB)

if(BUILD_DOCKERAPP)
add_definitions(-DBUILD_DOCKERAPP)
endif(BUILD_DOCKERAPP)

if(BUILD_P11)
find_package(LibP11 REQUIRED)
add_definitions(-DBUILD_P11)
Expand Down
17 changes: 16 additions & 1 deletion src/aktualizr_lite/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ static int list_main(Config &config, const bpo::variables_map &unused) {
}
}

LOG_INFO << "Updates for available to " << hwid << ":";
LOG_INFO << "Updates available to " << hwid << ":";
for (auto &t : client->allTargets()) {
for (auto const &it : t.hardwareIds()) {
if (it == hwid) {
Expand All @@ -47,6 +47,21 @@ static int list_main(Config &config, const bpo::variables_map &unused) {
name = t.custom_version();
}
LOG_INFO << name << "\tsha256:" << t.sha256Hash();
if (config.pacman.type == PackageManager::kOstreeDockerApp) {
bool shown = false;
auto apps = t.custom_data()["docker_apps"];
for (Json::ValueIterator i = apps.begin(); i != apps.end(); ++i) {
if (!shown) {
shown = true;
LOG_INFO << "\tDocker Apps:";
}
if ((*i).isObject() && (*i).isMember("filename")) {
LOG_INFO << "\t\t" << i.key().asString() << " -> " << (*i)["filename"].asString();
} else {
LOG_ERROR << "\t\tInvalid custom data for docker-app: " << i.key().asString();
}
}
}
break;
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/libaktualizr/package_manager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ if(BUILD_OSTREE)
add_aktualizr_test(NAME ostree SOURCES ostreemanager_test.cc PROJECT_WORKING_DIRECTORY NO_VALGRIND
ARGS ${PROJECT_BINARY_DIR}/ostree_repo)

if(BUILD_DOCKERAPP)
target_sources(package_manager PRIVATE dockerappmanager.cc)
add_aktualizr_test(NAME dockerapp SOURCES dockerappmanager_test.cc PROJECT_WORKING_DIRECTORY NO_VALGRIND
ARGS ${PROJECT_BINARY_DIR}/ostree_repo "$<TARGET_FILE:aktualizr-repo>")
endif(BUILD_DOCKERAPP)
endif(BUILD_OSTREE)

add_aktualizr_test(NAME packagemanager_factory SOURCES packagemanagerfactory_test.cc NO_VALGRIND
Expand All @@ -55,6 +60,7 @@ aktualizr_source_file_checks(ostreemanager.cc ostreereposync.cc
ostreemanager.h ostreereposync.h)

aktualizr_source_file_checks(androidmanager.cc androidmanager.h)
aktualizr_source_file_checks(dockerappmanager.cc dockerappmanager.h dockerappmanager_test.cc)

if(ANDROID)
target_sources(package_manager PRIVATE androidmanager.cc)
Expand Down
29 changes: 29 additions & 0 deletions src/libaktualizr/package_manager/docker_fake.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#! /bin/bash
set -eEuo pipefail

if [ "$1" = "app" ] ; then
echo "DOCKER-APP RENDER OUTPUT"
if [ ! -f app1.dockerapp ] ; then
echo "Missing docker app file!"
exit 1
fi
cat app1.dockerapp
exit 0
fi
if [ "$1" = "up" ] ; then
echo "DOCKER-COMPOSE UP"
if [ ! -f docker-compose.yml ] ; then
echo "Missing docker-compose file!"
exit 1
fi
# the content of dockerapp includes the sha of the target, so this should
# be present in the docker-compose.yml it creates.
if ! grep primary docker-compose.yml ; then
echo "Could not find expected content in docker-compose.yml"
cat docker-compose.yml
exit 1
fi
exit 0
fi
echo "Unknown command: $*"
exit 1
34 changes: 34 additions & 0 deletions src/libaktualizr/package_manager/dockerapp_test_repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#! /bin/bash
set -eEuo pipefail

if [ "$#" -lt 3 ]; then
echo "Usage: $0 <aktualizr-repo> <output directory> <port>"
exit 1
fi

AKTUALIZR_REPO="$1"
DEST_DIR="$2"
PORT="$3"

akrepo() {
"$AKTUALIZR_REPO" --path "$DEST_DIR" "$@"
}

mkdir -p "$DEST_DIR"
trap 'rm -rf "$DEST_DIR"' ERR

IMAGES=$(mktemp -d)
trap 'rm -rf "$IMAGES"' exit
DOCKER_APP="$IMAGES/foo.dockerapp"
echo "fake contents of a docker app" > "$DOCKER_APP"

akrepo --command generate --expires 2021-07-04T16:33:27Z
akrepo --command image --filename "$DOCKER_APP" --targetname foo.dockerapp
akrepo --command addtarget --hwid primary_hw --serial CA:FE:A6:D2:84:9D --targetname foo.dockerapp
akrepo --command signtargets

cd $DEST_DIR
echo "Target.json is: "
cat repo/image/targets.json
echo "Running repo server port on $PORT"
exec python3 -m http.server $PORT
116 changes: 116 additions & 0 deletions src/libaktualizr/package_manager/dockerappmanager.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include "dockerappmanager.h"

#include <sstream>

struct DockerApp {
DockerApp(const std::string app_name, const PackageConfig &config)
: name(std::move(app_name)),
app_root(std::move(config.docker_apps_root / app_name)),
app_params(std::move(config.docker_app_params)),
app_bin(std::move(config.docker_app_bin)),
compose_bin(std::move(config.docker_compose_bin)) {}

bool render(const std::string &app_content) {
auto bin = boost::filesystem::canonical(app_bin).string();
Utils::writeFile(app_root / (name + ".dockerapp"), app_content);
std::string cmd("cd " + app_root.string() + " && " + bin + " app render " + name);
if (!app_params.empty()) {
cmd += " -f " + app_params.string();
}
std::string yaml;
if (Utils::shell(cmd, &yaml, true) != 0) {
LOG_ERROR << "Unable to run " << cmd << " output:\n" << yaml;
return false;
}
Utils::writeFile(app_root / "docker-compose.yml", yaml);
return true;
}

bool start() {
// Depending on the number and size of the containers in the docker-app,
// this command can take a bit of time to complete. Rather than using,
// Utils::shell which isn't interactive, we'll use std::system so that
// stdout/stderr is streamed while docker sets things up.
auto bin = boost::filesystem::canonical(compose_bin).string();
std::string cmd("cd " + app_root.string() + " && " + bin + " up --remove-orphans -d");
if (std::system(cmd.c_str()) != 0) {
return false;
}
return true;
}

std::string name;
boost::filesystem::path app_root;
boost::filesystem::path app_params;
boost::filesystem::path app_bin;
boost::filesystem::path compose_bin;
};

bool DockerAppManager::iterate_apps(const Uptane::Target &target, DockerAppCb cb) const {
auto apps = target.custom_data()["docker_apps"];
bool res = true;
Uptane::ImagesRepository repo;
// checkMetaOffline pulls in data from INvStorage to properly initialize
// the targets member of the instance so that we can use the LazyTargetList
repo.checkMetaOffline(*storage_);

if (!apps) {
LOG_DEBUG << "Detected an update target from Director with no docker-apps data";
for (const auto t : Uptane::LazyTargetsList(repo, storage_, fake_fetcher_)) {
if (t == target) {
LOG_DEBUG << "Found the match " << t;
apps = t.custom_data()["docker_apps"];
break;
}
}
}

for (const auto t : Uptane::LazyTargetsList(repo, storage_, fake_fetcher_)) {
for (Json::ValueIterator i = apps.begin(); i != apps.end(); ++i) {
if ((*i).isObject() && (*i).isMember("filename")) {
for (auto app : config.docker_apps) {
if (i.key().asString() == app && (*i)["filename"].asString() == t.filename()) {
if (!cb(app, t)) {
res = false;
}
}
}
} else {
LOG_ERROR << "Invalid custom data for docker-app: " << i.key().asString() << " -> " << *i;
}
}
}
return res;
}

bool DockerAppManager::fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys,
FetcherProgressCb progress_cb, const api::FlowControlToken *token) {
if (!OstreeManager::fetchTarget(target, fetcher, keys, progress_cb, token)) {
return false;
}

LOG_INFO << "Looking for DockerApps to fetch";
auto cb = [this, &fetcher, &keys, progress_cb, token](const std::string &app, const Uptane::Target &app_target) {
LOG_INFO << "Fetching " << app << " -> " << app_target;
return PackageManagerInterface::fetchTarget(app_target, fetcher, keys, progress_cb, token);
};
return iterate_apps(target, cb);
}

data::InstallationResult DockerAppManager::install(const Uptane::Target &target) const {
auto res = OstreeManager::install(target);
auto cb = [this](const std::string &app, const Uptane::Target &app_target) {
LOG_INFO << "Installing " << app << " -> " << app_target;
std::stringstream ss;
ss << *storage_->openTargetFile(app_target);
DockerApp dapp(app, config);
if (!dapp.render(ss.str()) || !dapp.start()) {
return false;
}
return true;
};
if (!iterate_apps(target, cb)) {
return data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, "Could not render docker app");
}
return res;
}
28 changes: 28 additions & 0 deletions src/libaktualizr/package_manager/dockerappmanager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#ifndef DOCKERAPPMGR_H_
#define DOCKERAPPMGR_H_

#include "ostreemanager.h"
#include "uptane/iterator.h"

using DockerAppCb = std::function<bool(const std::string &app, const Uptane::Target &app_target)>;

class DockerAppManager : public OstreeManager {
public:
DockerAppManager(PackageConfig pconfig, std::shared_ptr<INvStorage> storage, std::shared_ptr<Bootloader> bootloader,
std::shared_ptr<HttpInterface> http)
: OstreeManager(pconfig, storage, bootloader, http) {
fake_fetcher_ = std::make_shared<Uptane::Fetcher>(Config(), http_);
}
bool fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys,
FetcherProgressCb progress_cb, const api::FlowControlToken *token = nullptr) override;
data::InstallationResult install(const Uptane::Target &target) const override;
std::string name() const override { return "ostree+docker-app"; }

private:
bool iterate_apps(const Uptane::Target &target, DockerAppCb cb) const;

// iterate_apps needs an Uptane::Fetcher. However, its an unused parameter
// and we just need to construct a dummy one to make the compiler happy.
std::shared_ptr<Uptane::Fetcher> fake_fetcher_;
};
#endif // DOCKERAPPMGR_H_
Loading

0 comments on commit 06ca003

Please sign in to comment.