Aktualizr
C++ SOTA Client
dockerappmanager_test.cc
1 #include <gtest/gtest.h>
2 
3 #include <boost/filesystem.hpp>
4 #include <boost/process.hpp>
5 
6 #include "http/httpclient.h"
7 #include "libaktualizr/config.h"
8 #include "libaktualizr/packagemanagerfactory.h"
9 #include "libaktualizr/packagemanagerinterface.h"
10 #include "package_manager/dockerappmanager.h"
11 #include "primary/sotauptaneclient.h"
12 #include "storage/invstorage.h"
13 #include "test_utils.h"
14 #include "uptane/fetcher.h"
15 
16 static std::string repo_server = "http://127.0.0.1:";
17 static std::string treehub_server = "http://127.0.0.1:";
18 static boost::filesystem::path test_sysroot;
19 static boost::filesystem::path uptane_gen;
20 
21 static struct {
22  int serial{0};
23  std::string rev;
24 } ostree_deployment;
25 static std::string new_rev;
26 
27 extern "C" OstreeDeployment* ostree_sysroot_get_booted_deployment(OstreeSysroot* self) {
28  (void)self;
29  static GObjectUniquePtr<OstreeDeployment> dep;
30 
31  dep.reset(ostree_deployment_new(0, "dummy-os", ostree_deployment.rev.c_str(), ostree_deployment.serial,
32  ostree_deployment.rev.c_str(), ostree_deployment.serial));
33  return dep.get();
34 }
35 
36 extern "C" const char* ostree_deployment_get_csum(OstreeDeployment* self) {
37  (void)self;
38  return ostree_deployment.rev.c_str();
39 }
40 
41 static void progress_cb(const Uptane::Target& target, const std::string& description, unsigned int progress) {
42  (void)description;
43  LOG_INFO << "progress_cb " << target << " " << progress;
44 }
45 
46 static std::unique_ptr<boost::process::child> create_repo(const boost::filesystem::path& repo_path) {
47  std::string port = TestUtils::getFreePort();
48  repo_server += port;
49  auto p = std_::make_unique<boost::process::child>("src/libaktualizr/package_manager/dockerapp_test_repo.sh",
50  uptane_gen, repo_path, port);
51  TestUtils::waitForServer(repo_server + "/");
52  return p;
53 }
54 
55 TEST(DockerAppManager, PackageManager_Factory_Good) {
56  Config config;
57  config.pacman.type = PACKAGE_MANAGER_OSTREEDOCKERAPP;
58  config.pacman.sysroot = test_sysroot;
60  config.storage.path = dir.Path();
61 
62  std::shared_ptr<INvStorage> storage = INvStorage::newStorage(config.storage);
63  auto pacman = PackageManagerFactory::makePackageManager(config.pacman, config.bootloader, storage, nullptr);
64  EXPECT_TRUE(pacman);
65 }
66 
67 TEST(DockerAppManager, DockerAppConfig) {
68  Config config;
69  config.pacman.type = PACKAGE_MANAGER_OSTREEDOCKERAPP;
70  config.pacman.sysroot = test_sysroot.string();
71  config.pacman.ostree_server = treehub_server;
72  config.pacman.extra["docker_apps_root"] = "apps-root";
73  config.pacman.extra["docker_apps"] = "app1 app2";
74  config.pacman.extra["docker_app_bin"] = "dab";
75  config.pacman.extra["docker_compose_bin"] = "compose";
76 
77  DockerAppManagerConfig dacfg(config.pacman);
78  ASSERT_TRUE(dacfg.docker_prune);
79  ASSERT_EQ(2, dacfg.docker_apps.size());
80  ASSERT_EQ("app1", dacfg.docker_apps[0]);
81  ASSERT_EQ("app2", dacfg.docker_apps[1]);
82  ASSERT_EQ("apps-root", dacfg.docker_apps_root);
83  ASSERT_EQ("dab", dacfg.docker_app_bin);
84  ASSERT_EQ("compose", dacfg.docker_compose_bin);
85 
86  config.pacman.extra["docker_prune"] = "0";
87  dacfg = DockerAppManagerConfig(config.pacman);
88  ASSERT_FALSE(dacfg.docker_prune);
89 
90  config.pacman.extra["docker_prune"] = "FALSE";
91  dacfg = DockerAppManagerConfig(config.pacman);
92  ASSERT_FALSE(dacfg.docker_prune);
93 }
94 
96  std::string sha = Utils::readFile(test_sysroot / "ostree/repo/refs/heads/ostree/1/1/0", true);
97  Json::Value target_json;
98  target_json["hashes"]["sha256"] = sha;
99  target_json["custom"]["targetFormat"] = "OSTREE";
100  target_json["length"] = 0;
101  target_json["custom"]["docker_apps"]["app1"]["filename"] = "foo.dockerapp";
102  // Add a docker-app bundle entry to make sure we don't get confused
103  target_json["custom"]["docker_apps"]["app1"]["uri"] = "http://foo.com";
104  Uptane::Target target("pull", target_json);
105 
106  TemporaryDirectory temp_dir;
107  auto repo = temp_dir.Path();
108  auto repod = create_repo(repo);
109 
110  ostree_deployment.serial = 1;
111  ostree_deployment.rev = sha;
112 
113  boost::filesystem::path apps_root = temp_dir / "docker_apps";
114 
115  Config config;
116  config.pacman.type = PACKAGE_MANAGER_OSTREEDOCKERAPP;
117  config.pacman.sysroot = test_sysroot.string();
118  config.pacman.ostree_server = treehub_server;
119  config.pacman.extra["docker_apps_root"] = apps_root.string();
120  config.pacman.extra["docker_apps"] = "app1 app2";
121  config.pacman.extra["docker_app_bin"] = config.pacman.extra["docker_compose_bin"] =
122  "src/libaktualizr/package_manager/docker_fake.sh";
123  config.uptane.repo_server = repo_server + "/repo/repo";
124  TemporaryDirectory dir;
125  config.storage.path = dir.Path();
126 
127  // Create a fake "docker-app" that's not configured. We'll test below
128  // to ensure its removed
129  boost::filesystem::create_directories(apps_root / "delete-this-app");
130  // This is app is configured, but not a part of the install Target, so
131  // it should get removed below
132  boost::filesystem::create_directories(apps_root / "app2");
133 
134  std::shared_ptr<INvStorage> storage = INvStorage::newStorage(config.storage);
135  storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kCurrent);
136  KeyManager keys(storage, config.keymanagerConfig());
137  auto client = std_::make_unique<SotaUptaneClient>(config, storage);
138  ASSERT_NO_THROW(client->updateImageMeta());
139 
140  std::string targets = Utils::readFile(repo / "repo/repo/targets.json");
141  LOG_INFO << "Repo targets " << targets;
142 
143  bool result = client->package_manager_->fetchTarget(target, *(client->uptane_fetcher), keys, progress_cb, nullptr);
144  ASSERT_TRUE(result);
145 
146  auto hashes = std::vector<Hash>{
147  Hash(Hash::Type::kSha256, "dfca385c923400228c8ddd3c2d572919985e48a9409a2d71dab33148017231c3"),
148  Hash(Hash::Type::kSha512,
149  "76b183d51f53613a450825afc6f984077b68ae7b321ba041a2b3871f3c25a9a20d964ad0b60352e5fdd09b78fd53879f4e3"
150  "fa3dcc8335b26d3bbf455803d2ecb")};
151  Uptane::Target app_target("foo.dockerapp", Uptane::EcuMap{}, hashes, 8);
152  ASSERT_TRUE(client->package_manager_->checkTargetFile(app_target));
153 
154  client->package_manager_->install(target);
155  std::string content = Utils::readFile(apps_root / "app1/docker-compose.yml");
156  ASSERT_EQ("DOCKER-APP RENDER OUTPUT\nfake contents of a docker app\n", content);
157 
158  // Make sure the unconfigured docker app has been removed:
159  ASSERT_FALSE(boost::filesystem::exists(apps_root / "delete-this-app"));
160  ASSERT_FALSE(boost::filesystem::exists(apps_root / "app2"));
161  ASSERT_TRUE(boost::filesystem::exists(apps_root / "docker-compose-down-called"));
162 
163  setenv("DOCKER_APP_FAIL", "1", 1);
164  result = client->package_manager_->fetchTarget(target, *(client->uptane_fetcher), keys, progress_cb, nullptr);
165  ASSERT_FALSE(result);
166 }
167 
169  // Define the target we want to update to
170  unsetenv("DOCKER_APP_FAIL"); // Make sure this is cleared from previous test
171  std::string sha = Utils::readFile(test_sysroot / "ostree/repo/refs/heads/ostree/1/1/0", true);
172  Json::Value target_json;
173  target_json["hashes"]["sha256"] = sha;
174  target_json["custom"]["targetFormat"] = "OSTREE";
175  target_json["length"] = 0;
176  target_json["custom"]["docker_apps"]["app1"]["uri"] = "hub.docker.io/user/hello@sha256:deadbeef";
177  // Add a standalone entry to make sure we don't get confused
178  target_json["custom"]["docker_apps"]["app1"]["filename"] = "foo.dockerapp";
179  Uptane::Target target("pull", target_json);
180 
181  ostree_deployment.serial = 1;
182  ostree_deployment.rev = sha;
183 
184  TemporaryDirectory temp_dir;
185 
186  // Insert a facke "docker" binary into our path
187  boost::filesystem::path docker =
188  boost::filesystem::system_complete("src/libaktualizr/package_manager/docker_fake.sh");
189  ASSERT_EQ(0, symlink(docker.c_str(), (temp_dir.Path() / "docker").c_str()));
190  std::string path(temp_dir.PathString() + ":" + getenv("PATH"));
191  setenv("PATH", path.c_str(), 1);
192 
193  // Set up a SotaUptaneClient to test with
194  boost::filesystem::path apps_root = temp_dir / "docker_apps";
195  Config config;
196  config.pacman.type = PACKAGE_MANAGER_OSTREEDOCKERAPP;
197  config.pacman.sysroot = test_sysroot;
198  config.pacman.extra["docker_apps_root"] = apps_root.string();
199  config.pacman.extra["docker_apps"] = "app1 app2";
200  config.pacman.extra["docker_compose_bin"] = "src/libaktualizr/package_manager/docker_fake.sh";
201  config.pacman.ostree_server = treehub_server;
202  config.uptane.repo_server = repo_server + "/repo/repo";
203  TemporaryDirectory dir;
204  config.storage.path = dir.Path();
205 
206  // Create a fake "docker-app" that's not configured. We'll test below
207  // to ensure its removed
208  // boost::filesystem::create_directories(apps_root / "delete-this-app");
209  // This is app is configured, but not a part of the install Target, so
210  // it should get removed below
211  boost::filesystem::create_directories(apps_root / "app2");
212 
213  std::shared_ptr<INvStorage> storage = INvStorage::newStorage(config.storage);
214  storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kCurrent);
215  KeyManager keys(storage, config.keymanagerConfig());
216  auto client = std_::make_unique<SotaUptaneClient>(config, storage);
217 
218  ASSERT_TRUE(client->package_manager_->fetchTarget(target, *(client->uptane_fetcher), keys, progress_cb, nullptr));
219  std::string content = Utils::readFile(temp_dir / "docker-app-pull");
220  ASSERT_EQ("PULL CALLED app pull hub.docker.io/user/hello@sha256:deadbeef\n", content);
221 
222  client->package_manager_->install(target);
223  content = Utils::readFile(apps_root / "app1/docker-compose.yml");
224  ASSERT_EQ("FAKE CONTENT FOR IMAGE RENDER\nhub.docker.io/user/hello@sha256:deadbeef\n", content);
225 
226  // Make sure the unconfigured docker app has been removed:
227  ASSERT_FALSE(boost::filesystem::exists(apps_root / "delete-this-app"));
228  ASSERT_FALSE(boost::filesystem::exists(apps_root / "app2"));
229  ASSERT_TRUE(boost::filesystem::exists(apps_root / "docker-compose-down-called"));
230 }
231 
232 #ifndef __NO_MAIN__
233 int main(int argc, char** argv) {
234  ::testing::InitGoogleTest(&argc, argv);
235 
236  if (argc != 3) {
237  std::cerr << "Error: " << argv[0]
238  << " requires the path to an OSTree sysroot and uptane-generator as an input argument.\n";
239  return EXIT_FAILURE;
240  }
241  uptane_gen = argv[2];
242 
243  std::string port = TestUtils::getFreePort();
244  treehub_server += port;
245  boost::process::child server_process("tests/fake_http_server/fake_test_server.py", port);
246 
247  TemporaryDirectory temp_dir;
248  // Utils::copyDir doesn't work here. Complaints about non existent symlink path
249  int r = system((std::string("cp -r ") + argv[1] + std::string(" ") + temp_dir.PathString()).c_str());
250  if (r != 0) {
251  return -1;
252  }
253  test_sysroot = (temp_dir.Path() / "ostree_repo").string();
254 
255  TestUtils::waitForServer(treehub_server + "/");
256 
257  return RUN_ALL_TESTS();
258 }
259 #endif
Hash
The Hash class The hash of a file or Uptane metadata.
Definition: types.h:159
DockerAppManagerConfig
Definition: dockerappmanager.h:9
KeyManager
Definition: keymanager.h:13
DockerAppBundles
Definition: dockerappmanager.h:44
Config
Configuration object for an aktualizr instance running on a Primary ECU.
Definition: config.h:208
TemporaryDirectory
Definition: utils.h:82
DockerAppStandalone
Definition: dockerappmanager.h:21
result
Results of libaktualizr API calls.
Definition: results.h:12
Uptane::Target
Definition: types.h:379
DockerAppManager
Definition: dockerappmanager.h:67