Skip to content

Commit

Permalink
Revert "app: Use composectl to check whether app is installed"
Browse files Browse the repository at this point in the history
This reverts commit 37c0249.

The given functionality does not work if images hosted in docker.io are
specified in a docker compose project file in a full format.
For example, "docker.io/library/nginx:stable-alpine" or
"docker.io/homeassistant/home-assistant:stable".

The dockerd removes the "docker.io" or "docker.io/library" prefix when
stores such images in its store and as result returns the image URIs in
the shorten format in a response to "http://localhost/images/json?all=1"
request. For example, "nginx@sha256:<hash>" or
"homeassistant/home-assistant:<hash>".

Therefore, it is not possible to map images listed in the compose
project to images reported by dockerd unless the dockerd's URI shortening
rules are taken in the "isAppInstalled" logic.
  • Loading branch information
mike-sul committed Aug 26, 2024
1 parent d2cd79e commit 814f26a
Show file tree
Hide file tree
Showing 6 changed files with 14 additions and 57 deletions.
24 changes: 0 additions & 24 deletions src/composeapp/appengine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -131,30 +131,6 @@ bool AppEngine::isAppFetched(const App& app) const {
return res;
}

bool AppEngine::isAppInstalled(const App& app) const {
bool res{false};
try {
std::future<std::string> output;
exec(boost::format{"%s --store %s check %s --local --install --format json"} % composectl_cmd_ % storeRoot() %
app.uri,
"", boost::process::std_out > output);
const std::string output_str{output.get()};
const auto app_fetch_status{Utils::parseJSON(output_str)};
if (app_fetch_status.isMember("install_check") && app_fetch_status["install_check"].isMember(app.uri) &&
app_fetch_status["install_check"][app.uri].isMember("missing_images") &&
(app_fetch_status["install_check"][app.uri]["missing_images"].isNull() ||
app_fetch_status["install_check"][app.uri]["missing_images"].empty())) {
res = true;
}
} catch (const ExecError& exc) {
LOG_DEBUG << "app is not fully fetched or installed; app: " << app.name << ", status: " << exc.what();
} catch (const std::exception& exc) {
LOG_ERROR << "failed to verify whether app is installed; app: " << app.name << ", err: " << exc.what();
throw;
}
return res;
}

void AppEngine::installAppAndImages(const App& app) {
exec(boost::format{"%s --store %s --compose %s --host %s install %s"} % composectl_cmd_ % storeRoot() %
installRoot() % dockerHost() % app.uri,
Expand Down
1 change: 0 additions & 1 deletion src/composeapp/appengine.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class AppEngine : public Docker::RestorableAppEngine {

private:
bool isAppFetched(const App& app) const override;
bool isAppInstalled(const App& app) const override;
void installAppAndImages(const App& app) override;

const std::string composectl_cmd_;
Expand Down
2 changes: 1 addition & 1 deletion src/docker/restorableappengine.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ class RestorableAppEngine : public AppEngine {
const StorageSpaceFunc& storageSpaceFunc() const { return storage_space_func_; }

virtual bool isAppFetched(const App& app) const;
virtual bool isAppInstalled(const App& app) const;
virtual void installAppAndImages(const App& app);

private:
Expand All @@ -139,6 +138,7 @@ class RestorableAppEngine : public AppEngine {
void installAppImages(const boost::filesystem::path& app_dir);

bool areAppImagesFetched(const App& app) const;
bool isAppInstalled(const App& app) const;

// check if App&Images are running
static bool isRunning(const App& app, const std::string& compose_file,
Expand Down
4 changes: 1 addition & 3 deletions src/liteclient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ LiteClient::LiteClient(Config config_in, const AppEngine::Ptr& app_engine, const
} else {
throw std::runtime_error("Unsupported package manager type: " + config.pacman.type);
}
if (config.pacman.extra.count("x-fio-test-no-init-target") == 0) {
basepacman->setInitialTargetIfNeeded(config.provision.primary_ecu_hardware_id);
}
basepacman->setInitialTargetIfNeeded(config.provision.primary_ecu_hardware_id);
package_manager_ = basepacman;
// After running `setInitialTargetIfNeeded` details of the current target are available, so
// we should update value of the request headers that are related to the current Target description
Expand Down
21 changes: 12 additions & 9 deletions tests/aklite_offline_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ class AkliteOffline : public ::testing::Test {
// emulate registration of images located in `/var/lib/docker/image/overlay2/repositories.json
const boost::filesystem::path repositories_file{daemon_.dir() / "/image/overlay2/repositories.json"};
Json::Value repositories;
Json::Value images{Utils::parseJSONFile(daemon_.dir() / "images.json")};
Json::Value images;
if (boost::filesystem::exists(repositories_file)) {
repositories = Utils::parseJSONFile(repositories_file.string());
} else {
Expand Down Expand Up @@ -821,7 +821,6 @@ TEST_F(AkliteOffline, RollbackToInitialTarget) {
// at a device initial startup, so we just check if name and ostree hash of targets match.
ASSERT_EQ(initial_target_.Name(), getCurrent().Name());
ASSERT_EQ(initial_target_.Sha256Hash(), getCurrent().Sha256Hash());
ASSERT_TRUE(Target::isInitial(Target::fromTufTarget(getCurrent())));
}

TEST_F(AkliteOffline, RollbackToInitialTargetIfAppDrivenRolllback) {
Expand All @@ -838,43 +837,47 @@ TEST_F(AkliteOffline, RollbackToInitialTargetIfAppDrivenRolllback) {
ASSERT_EQ(aklite::cli::StatusCode::Ok, run());
ASSERT_EQ(initial_target_.Name(), getCurrent().Name());
ASSERT_EQ(initial_target_.Sha256Hash(), getCurrent().Sha256Hash());
ASSERT_TRUE(Target::isInitial(Target::fromTufTarget(getCurrent())));
}

TEST_F(AkliteOffline, RollbackToUnknown) {
// make the initial Target "unknown"
cfg_.pacman.extra["x-fio-test-no-init-target"] = true;
const auto app01{createApp("app-01")};
preloadApps({app01}, {}, false);
// remove the current target app from the store/install source dir
boost::filesystem::remove_all(app_store_.appsDir());
const auto new_target{addTarget({createApp("app-01")})};

// make the initial Target setting fail, so the current Target is "unknown"
const Docker::Uri uri{Docker::Uri::parseUri(app01.uri)};
const auto app_dir{test_dir_.Path() / "reset-apps" / "apps" / uri.app / uri.digest.hash()};
Utils::writeFile(app_dir / Docker::Manifest::Filename, std::string("broken json"));

ASSERT_EQ(aklite::cli::StatusCode::InstallNeedsReboot, install());
reboot();
// emulate "normal" rollback - boot on the previous target, which is "unknown"
sys_repo_.deploy(initial_target_.Sha256Hash());
ASSERT_EQ(aklite::cli::StatusCode::InstallRollbackOk, run());
ASSERT_EQ(initial_target_, getCurrent());
ASSERT_TRUE(Target::isInitial(Target::fromTufTarget(getCurrent())));
ASSERT_EQ(initial_target_.Sha256Hash(), getCurrent().Sha256Hash());
ASSERT_FALSE(Target::isInitial(Target::fromTufTarget(getCurrent())));
}

TEST_F(AkliteOffline, RollbackToUnknownIfAppDrivenRolllback) {
// make the initial Target "unknown"
cfg_.pacman.extra["x-fio-test-no-init-target"] = true;
const auto app01{createApp("app-01")};
preloadApps({app01}, {}, false);

// remove the current target app from the store/install source dir
boost::filesystem::remove_all(app_store_.appsDir());
const auto new_target{addTarget({createApp("app-01", "compose-start-failure")})};

// make the initial Target setting fail, so the current Target is "unknown"
const Docker::Uri uri{Docker::Uri::parseUri(app01.uri)};
const auto app_dir{test_dir_.Path() / "reset-apps" / "apps" / uri.app / uri.digest.hash()};
Utils::writeFile(app_dir / Docker::Manifest::Filename, std::string("broken json"));
ASSERT_EQ(aklite::cli::StatusCode::InstallNeedsReboot, install());
reboot();
ASSERT_EQ(aklite::cli::StatusCode::InstallRollbackFailed, run());
// Cannot perform rollback to unknown, so still booted on a new target's hash
ASSERT_EQ(new_target.Sha256Hash(), getCurrent().Sha256Hash());
ASSERT_FALSE(Target::isInitial(Target::fromTufTarget(getCurrent())));
}

TEST_F(AkliteOffline, InvalidTargetInstallOstree) {
Expand Down
19 changes: 0 additions & 19 deletions tests/docker-daemon_fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,6 @@ def do_GET(self):
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.send_header('Api-Version:', API_VERSION)
self.end_headers()
elif self.path.find('/images/json') != -1:
dockerd_response = []
try:
with open(os.path.join(self.server.root_dir, "images.json"), "r") as f:
images = json.load(f)
for image in images:
dockerd_response.append({
"RepoDigests": [image]
})
except FileNotFoundError:
pass
except Exception as exc:
logger.error(exc)

logger.info(">>> sending response %s" % json.dumps(dockerd_response))
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(dockerd_response).encode())
else:
self.send_response(200)
self.send_header('Api-Version:', API_VERSION)
Expand Down

0 comments on commit 814f26a

Please sign in to comment.