Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
dockerappmanager.cc
1 #include "dockerappmanager.h"
2 
3 #include <sstream>
4 
5 /**
6  * @brief This package manager compliments the OSTreePackageManager by also including optional Docker Apps.
7  *
8  * A full description of the Docker App project can be found here:
9  * https://github.com/docker/app/
10  *
11  * Docker Apps are very analogous to docker-compose. In fact, this module
12  * currently "renders" the docker-app file into a docker-compose file. Each
13  * Docker App appears as a Target in the TUF targets list. Each OStree target
14  * can then reference these docker apps in its custom data section. eg:
15  *
16  * "targets": {
17  * httpd.dockerapp-1 : {
18  * "custom" : {"hardwareIds" : ["all"], "name" : "httpd.dockerapp", "version" : "1"},
19  * "hashes" : {"sha256" : "f0ad4e3ce6a5e9cb70c9d747e977fddfacd08419deec0714622029b12dde8338"},
20  * "length" : 889
21  * },
22  * "raspberrypi3-64-lmp-144" : {
23  * "custom" : {
24  * "docker_apps" : {
25  * "httpd" : {
26  * "filename" : "httpd.dockerapp-1"
27  * }
28  * },
29  * "hardwareIds" : ["raspberrypi3-64"],
30  * "name" : "raspberrypi3-64-lmp",
31  * "targetFormat" : "OSTREE",
32  * "version" : "144"
33  * },
34  * "hashes" : {"sha256" : "20ac4f7cd50cda6bfed0caa1f8231cc9a7e40bec60026c66df5f7e143af96942"},
35  * "length" : 0
36  * }
37  * }
38  */
39 
40 struct DockerApp {
41  DockerApp(std::string app_name, const PackageConfig &config)
42  : name(std::move(app_name)),
43  app_root(config.docker_apps_root / name),
44  app_params(config.docker_app_params),
45  app_bin(config.docker_app_bin),
46  compose_bin(config.docker_compose_bin) {}
47 
48  bool render(const std::string &app_content, bool persist) {
49  auto bin = boost::filesystem::canonical(app_bin).string();
50  Utils::writeFile(app_root / (name + ".dockerapp"), app_content);
51  std::string cmd("cd " + app_root.string() + " && " + bin + " render " + name);
52  if (!app_params.empty()) {
53  cmd += " --parameters-file " + app_params.string();
54  }
55  std::string yaml;
56  if (Utils::shell(cmd, &yaml, true) != 0) {
57  LOG_ERROR << "Unable to run " << cmd << " output:\n" << yaml;
58  return false;
59  }
60  if (persist) {
61  Utils::writeFile(app_root / "docker-compose.yml", yaml);
62  }
63  return true;
64  }
65 
66  bool start() {
67  // Depending on the number and size of the containers in the docker-app,
68  // this command can take a bit of time to complete. Rather than using,
69  // Utils::shell which isn't interactive, we'll use std::system so that
70  // stdout/stderr is streamed while docker sets things up.
71  auto bin = boost::filesystem::canonical(compose_bin).string();
72  std::string cmd("cd " + app_root.string() + " && " + bin + " up --remove-orphans -d");
73  return std::system(cmd.c_str()) == 0;
74  }
75 
76  void remove() {
77  auto bin = boost::filesystem::canonical(compose_bin).string();
78  std::string cmd("cd " + app_root.string() + " && " + bin + " down");
79  if (std::system(cmd.c_str()) == 0) {
80  boost::filesystem::remove_all(app_root);
81  } else {
82  LOG_ERROR << "docker-compose was unable to bring down: " << app_root;
83  }
84  }
85 
86  std::string name;
87  boost::filesystem::path app_root;
88  boost::filesystem::path app_params;
89  boost::filesystem::path app_bin;
90  boost::filesystem::path compose_bin;
91 };
92 
93 bool DockerAppManager::iterate_apps(const Uptane::Target &target, const DockerAppCb &cb) const {
94  auto apps = target.custom_data()["docker_apps"];
95  bool res = true;
97  // checkMetaOffline pulls in data from INvStorage to properly initialize
98  // the targets member of the instance so that we can use the LazyTargetList
99  repo.checkMetaOffline(*storage_);
100 
101  if (!apps) {
102  LOG_DEBUG << "Detected an update target from Director with no docker-apps data";
103  for (const auto &t : Uptane::LazyTargetsList(repo, storage_, fake_fetcher_)) {
104  if (t.MatchTarget(target)) {
105  LOG_DEBUG << "Found the match " << t;
106  apps = t.custom_data()["docker_apps"];
107  break;
108  }
109  }
110  }
111 
112  for (const auto &t : Uptane::LazyTargetsList(repo, storage_, fake_fetcher_)) {
113  for (Json::ValueIterator i = apps.begin(); i != apps.end(); ++i) {
114  if ((*i).isObject() && (*i).isMember("filename")) {
115  for (const auto &app : config.docker_apps) {
116  if (i.key().asString() == app && (*i)["filename"].asString() == t.filename()) {
117  if (!cb(app, t)) {
118  res = false;
119  }
120  }
121  }
122  } else {
123  LOG_ERROR << "Invalid custom data for docker-app: " << i.key().asString() << " -> " << *i;
124  }
125  }
126  }
127  return res;
128 }
129 
130 bool DockerAppManager::fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys,
131  FetcherProgressCb progress_cb, const api::FlowControlToken *token) {
132  if (!OstreeManager::fetchTarget(target, fetcher, keys, progress_cb, token)) {
133  return false;
134  }
135 
136  LOG_INFO << "Looking for DockerApps to fetch";
137  auto cb = [this, &fetcher, &keys, progress_cb, token](const std::string &app, const Uptane::Target &app_target) {
138  LOG_INFO << "Fetching " << app << " -> " << app_target;
139  return PackageManagerInterface::fetchTarget(app_target, fetcher, keys, progress_cb, token);
140  };
141  return iterate_apps(target, cb);
142 }
143 
144 data::InstallationResult DockerAppManager::install(const Uptane::Target &target) const {
145  auto res = OstreeManager::install(target);
146  handleRemovedApps(target);
147  auto cb = [this](const std::string &app, const Uptane::Target &app_target) {
148  LOG_INFO << "Installing " << app << " -> " << app_target;
149  std::stringstream ss;
150  ss << *storage_->openTargetFile(app_target);
151  DockerApp dapp(app, config);
152  return dapp.render(ss.str(), true) && dapp.start();
153  };
154  if (!iterate_apps(target, cb)) {
155  return data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, "Could not render docker app");
156  }
157  return res;
158 }
159 
160 // Handle the case like:
161 // 1) sota.toml is configured with 2 docker apps: "app1, app2"
162 // 2) update is applied, so we are now running both app1 and app2
163 // 3) sota.toml is updated with 1 docker app: "app1"
164 // At this point we should stop app2 and remove it.
165 void DockerAppManager::handleRemovedApps(const Uptane::Target &target) const {
166  if (!boost::filesystem::is_directory(config.docker_apps_root)) {
167  LOG_DEBUG << "config.docker_apps_root does not exist";
168  return;
169  }
170 
171  std::vector<std::string> target_apps = target.custom_data()["docker_apps"].getMemberNames();
172 
173  for (auto &entry : boost::make_iterator_range(boost::filesystem::directory_iterator(config.docker_apps_root), {})) {
174  if (boost::filesystem::is_directory(entry)) {
175  std::string name = entry.path().filename().native();
176  if (std::find(config.docker_apps.begin(), config.docker_apps.end(), name) == config.docker_apps.end()) {
177  LOG_WARNING << "Docker App(" << name
178  << ") installed, but is now removed from configuration. Removing from system";
179  DockerApp(name, config).remove();
180  }
181  if (std::find(target_apps.begin(), target_apps.end(), name) == target_apps.end()) {
182  LOG_WARNING << "Docker App(" << name
183  << ") configured, but not defined in installation target. Removing from system";
184  DockerApp(name, config).remove();
185  }
186  }
187  }
188 }
189 
190 TargetStatus DockerAppManager::verifyTarget(const Uptane::Target &target) const {
191  TargetStatus status;
192  if (target.IsOstree()) {
193  status = OstreeManager::verifyTarget(target);
194  if (status != TargetStatus::kGood) {
195  return status;
196  }
197  }
198  auto cb = [this](const std::string &app, const Uptane::Target &app_target) {
199  LOG_INFO << "Verifying " << app << " -> " << app_target;
200  std::stringstream ss;
201  ss << *storage_->openTargetFile(app_target);
202  DockerApp dapp(app, config);
203  return dapp.render(ss.str(), false);
204  };
205  if (!iterate_apps(target, cb)) {
206  return TargetStatus::kInvalid;
207  }
208  return TargetStatus::kGood;
209 }
Uptane::Fetcher
Definition: fetcher.h:33
KeyManager
Definition: keymanager.h:13
DockerApp
This package manager compliments the OSTreePackageManager by also including optional Docker Apps.
Definition: dockerappmanager.cc:40
data::InstallationResult
Definition: types.h:179
Uptane::LazyTargetsList
Definition: iterator.h:12
api::FlowControlToken
Provides a thread-safe way to pause and terminate task execution.
Definition: apiqueue.h:19
Uptane::Target::IsOstree
bool IsOstree() const
Is this an OSTree target? OSTree targets need special treatment because the hash doesn't represent th...
Definition: tuf.cc:220
PackageConfig
Definition: packagemanagerconfig.h:13
Uptane::Target
Definition: tuf.h:238
data::ResultCode::Numeric::kInstallFailed
Package installation failed.
Uptane::ImagesRepository
Definition: imagesrepository.h:13