This repository has been archived by the owner on May 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1189 from doanac/docker-appmgr-2
RFC: Add a docker-app package manager
- Loading branch information
Showing
15 changed files
with
414 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
Oops, something went wrong.