From b990a58ce2dc2a43a8c43c595945389ed108b25a Mon Sep 17 00:00:00 2001 From: Mike Sul Date: Tue, 14 Nov 2023 11:30:40 +0100 Subject: [PATCH 1/3] main: Remove unused function The former update function was replaced with the new one that utilizes the public API to accomplish an OTA update. Signed-off-by: Mike Sul --- src/main.cc | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/src/main.cc b/src/main.cc index 019edf7f..f3479cd4 100644 --- a/src/main.cc +++ b/src/main.cc @@ -222,42 +222,6 @@ static data::ResultCode::Numeric do_app_sync(LiteClient& client) { return client.install(target); } -static int update_main(LiteClient& client, const bpo::variables_map& variables_map) { - FileLock lock; - client.importRootMetaIfNeededAndPresent(); - client.finalizeInstall(); - Uptane::HardwareIdentifier hwid(client.config.provision.primary_ecu_hardware_id); - - std::string version("latest"); - - if (variables_map.count("update-name") > 0) { - version = variables_map["update-name"].as(); - } - - // This is only available if -DALLOW_MANUAL_ROLLBACK is set in the CLI args below. - if (variables_map.count("clear-installed-versions") > 0) { - LOG_WARNING << "Clearing installed version history!!!"; - client.storage->clearInstalledVersions(); - } - - LOG_INFO << "Finding " << version << " to update to..."; - auto find_target_res = find_target(client, hwid, client.tags, version); - if (find_target_res.first) { - std::string reason = "Manual update to " + version; - data::ResultCode::Numeric rc; - DownloadResultWithStat dr; - std::string cor_id; - std::tie(rc, dr, cor_id) = do_update(client, *find_target_res.second, reason); - - return (rc == data::ResultCode::Numeric::kNeedCompletion || rc == data::ResultCode::Numeric::kOk) ? EXIT_SUCCESS - : EXIT_FAILURE; - } else { - LOG_INFO << "No Target found to update to; hw ID: " << hwid.ToString() - << "; tags: " << boost::algorithm::join(client.tags, ","); - return EXIT_SUCCESS; - } -} - static int daemon_main(LiteClient& client, const bpo::variables_map& variables_map) { FileLock lock; if (client.config.uptane.repo_server.empty()) { From 7d5d6be1bda37c969f1ac63ab144d86391ffb827 Mon Sep 17 00:00:00 2001 From: Mike Sul Date: Mon, 6 Nov 2023 09:48:37 +0100 Subject: [PATCH 2/3] api: Add delayed App installation mode - Add a mechanism to support installation modes to the API in general. - Add "ostree only" or "delayed App installation" mode support. - Add unit test to verify the delayed App installation mode. Signed-off-by: Mike Sul --- include/aktualizr-lite/api.h | 19 ++++++++++- src/CMakeLists.txt | 1 + src/api.cc | 14 ++++---- src/composeappmanager.cc | 13 +++++++- src/composeappmanager.h | 1 + src/installer.h | 22 +++++++++++++ src/liteclient.cc | 28 +++++++++++----- src/liteclient.h | 6 ++-- src/rootfstreemanager.cc | 4 +++ src/rootfstreemanager.h | 6 ++-- tests/apiclient_test.cc | 62 ++++++++++++++++++++++++++++++++++++ 11 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 src/installer.h diff --git a/include/aktualizr-lite/api.h b/include/aktualizr-lite/api.h index 74a6a785..93007d5c 100644 --- a/include/aktualizr-lite/api.h +++ b/include/aktualizr-lite/api.h @@ -143,6 +143,23 @@ class DownloadResult { std::ostream &operator<<(std::ostream &os, const InstallResult &res); std::ostream &operator<<(std::ostream &os, const DownloadResult &res); +/** + * The installation mode to be applied. Specified during InstallContext context initialization. + */ +enum class InstallMode { + /** + * A default install mode. Both Target's components ostree and Apps are fetched and installed + * within InstallContext::Install() call. + */ + All = 0, + /** + * Fetch both ostree and Apps, but only install ostree if it has been updated. + * The fetched Apps are installed and started during the finalization phase, + * which is executed by the AkliteClient::CompleteInstallation() call. + */ + OstreeOnly +}; + class InstallContext { public: InstallContext(const InstallContext &) = delete; @@ -306,7 +323,7 @@ class AkliteClient { * Create an InstallContext object to help drive an update. */ std::unique_ptr Installer(const TufTarget &t, std::string reason = "", - std::string correlation_id = "") const; + std::string correlation_id = "", InstallMode = InstallMode::All) const; /** * @brief Complete a pending installation diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4d824179..57a7faca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,7 @@ set(HEADERS helpers.h yaml2json.h target.h downloader.h + installer.h exec.h cli/cli.h ../include/aktualizr-lite/api.h) diff --git a/src/api.cc b/src/api.cc index b1d6371d..e73a965e 100644 --- a/src/api.cc +++ b/src/api.cc @@ -205,13 +205,14 @@ std::string AkliteClient::GetDeviceID() const { return client_->getDeviceID(); } class LiteInstall : public InstallContext { public: - LiteInstall(std::shared_ptr client, std::unique_ptr t, std::string& reason) - : client_(std::move(client)), target_(std::move(t)), reason_(reason) {} + LiteInstall(std::shared_ptr client, std::unique_ptr t, std::string& reason, + InstallMode install_mode) + : client_(std::move(client)), target_(std::move(t)), reason_(reason), mode_{install_mode} {} InstallResult Install() override { client_->logTarget("Installing: ", *target_); - auto rc = client_->install(*target_); + auto rc = client_->install(*target_, mode_); auto status = InstallResult::Status::Failed; if (rc == data::ResultCode::Numeric::kNeedCompletion) { if (client_->isPendingTarget(*target_)) { @@ -287,6 +288,7 @@ class LiteInstall : public InstallContext { std::shared_ptr client_; std::unique_ptr target_; std::string reason_; + InstallMode mode_; }; bool AkliteClient::IsInstallationInProgress() const { return client_->getPendingTarget().IsValid(); } @@ -301,14 +303,14 @@ std::unique_ptr AkliteClient::CheckAppsInSync() const { auto correlation_id = target->custom_version() + "-" + boost::uuids::to_string(tmp); target->setCorrelationId(correlation_id); std::string reason = "Sync active target apps"; - installer = std::make_unique(client_, std::move(target), reason); + installer = std::make_unique(client_, std::move(target), reason, InstallMode::All); } client_->setAppsNotChecked(); return installer; } std::unique_ptr AkliteClient::Installer(const TufTarget& t, std::string reason, - std::string correlation_id) const { + std::string correlation_id, InstallMode install_mode) const { if (read_only_) { throw std::runtime_error("Can't perform this operation from read-only mode"); } @@ -341,7 +343,7 @@ std::unique_ptr AkliteClient::Installer(const TufTarget& t, std: throw std::runtime_error("Correlation ID's must be less than 64 bytes"); } target->setCorrelationId(correlation_id); - return std::make_unique(client_, std::move(target), reason); + return std::make_unique(client_, std::move(target), reason, install_mode); } InstallResult AkliteClient::CompleteInstallation() { diff --git a/src/composeappmanager.cc b/src/composeappmanager.cc index 471e3494..0cd38d1e 100644 --- a/src/composeappmanager.cc +++ b/src/composeappmanager.cc @@ -331,6 +331,13 @@ TargetStatus ComposeAppManager::verifyTarget(const Uptane::Target& target) const return ostree_target_status; } +data::InstallationResult ComposeAppManager::Install(const TufTarget& target, InstallMode mode) { + if (mode == InstallMode::OstreeOnly) { + return RootfsTreeManager::Install(target, mode); + } + return install(Target::fromTufTarget(target)); +} + data::InstallationResult ComposeAppManager::install(const Uptane::Target& target) const { // Stopping disabled apps before creating or starting new apps // because they may interfere with each other (e.g., using the same port). @@ -432,7 +439,11 @@ data::InstallationResult ComposeAppManager::finalizeInstall(const Uptane::Target // Stop disabled Apps before creating or starting new Apps since they may interfere with each other (e.g. the same // port is used). stopDisabledComposeApps(target); - LOG_INFO << "Starting Apps after successful boot on a new version of OSTree-based sysroot..."; + if (ir.description != "Already booted on the required version") { + LOG_INFO << "Starting Apps after successful boot on a new version of OSTree-based sysroot..."; + } else { + LOG_INFO << "Installing and starting Apps..."; + } // "finalize" (run) Apps that were pulled and created before reboot for (const auto& app_pair : getApps(target)) { const AppEngine::Result run_res = app_engine_->run({app_pair.first, app_pair.second}); diff --git a/src/composeappmanager.h b/src/composeappmanager.h index 9623dbee..2fd9198a 100644 --- a/src/composeappmanager.h +++ b/src/composeappmanager.h @@ -44,6 +44,7 @@ class ComposeAppManager : public RootfsTreeManager { std::string name() const override { return Name; } DownloadResultWithStat Download(const TufTarget& target) override; + data::InstallationResult Install(const TufTarget& target, InstallMode mode) override; bool fetchTarget(const Uptane::Target& target, Uptane::Fetcher& fetcher, const KeyManager& keys, const FetcherProgressCb& progress_cb, const api::FlowControlToken* token) override; diff --git a/src/installer.h b/src/installer.h new file mode 100644 index 00000000..c71d4d73 --- /dev/null +++ b/src/installer.h @@ -0,0 +1,22 @@ +#ifndef AKTUALIZR_LITE_INSTALLER_H_ +#define AKTUALIZR_LITE_INSTALLER_H_ + +#include "aktualizr-lite/api.h" +#include "libaktualizr/types.h" + +class Installer { + public: + // TODO: use the API's return type - InstallResult + virtual data::InstallationResult Install(const TufTarget& target, InstallMode mode) = 0; + + virtual ~Installer() = default; + Installer(const Installer&) = delete; + Installer(const Installer&&) = delete; + Installer& operator=(const Installer&) = delete; + Installer& operator=(const Installer&&) = delete; + + protected: + explicit Installer() = default; +}; + +#endif // AKTUALIZR_LITE_DOWNLOADER_H_ diff --git a/src/liteclient.cc b/src/liteclient.cc index e10fb45c..9ee638b2 100644 --- a/src/liteclient.cc +++ b/src/liteclient.cc @@ -115,6 +115,10 @@ LiteClient::LiteClient(Config& config_in, const AppEngine::Ptr& app_engine, cons if (!downloader_) { throw std::runtime_error("Invalid package manager: cannot cast to Downloader type"); } + installer_ = std::dynamic_pointer_cast(package_manager_); + if (!downloader_) { + throw std::runtime_error("Invalid package manager: cannot cast to Installer type"); + } sysroot_ = ostree_sysroot; } @@ -474,10 +478,10 @@ void LiteClient::writeCurrentTarget(const Uptane::Target& t) const { Utils::writeFile(config.storage.path / "current-target", ss.str()); } -data::InstallationResult LiteClient::installPackage(const Uptane::Target& target) { +data::InstallationResult LiteClient::installPackage(const Uptane::Target& target, InstallMode install_mode) { LOG_INFO << "Installing package using " << package_manager_->name() << " package manager"; try { - return package_manager_->install(target); + return installer_->Install(Target::toTufTarget(target), install_mode); } catch (std::exception& ex) { return data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, ex.what()); } @@ -639,9 +643,9 @@ DownloadResultWithStat LiteClient::download(const Uptane::Target& target, const return download_result; } -data::ResultCode::Numeric LiteClient::install(const Uptane::Target& target) { +data::ResultCode::Numeric LiteClient::install(const Uptane::Target& target, InstallMode install_mode) { notifyInstallStarted(target); - auto iresult = installPackage(target); + auto iresult = installPackage(target, install_mode); if (iresult.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) { LOG_INFO << "Update complete. Please reboot the device to activate"; is_reboot_required_ = (config.pacman.booted == BootedType::kBooted); @@ -655,10 +659,18 @@ data::ResultCode::Numeric LiteClient::install(const Uptane::Target& target) { storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kPending); } } else if (iresult.result_code.num_code == data::ResultCode::Numeric::kOk) { - LOG_INFO << "Update complete. No reboot needed"; - storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kCurrent); - writeCurrentTarget(target); - updateRequestHeaders(); + if (install_mode == InstallMode::OstreeOnly) { + // This is the case when the new Target updates just Apps and the ostree only mode is set. + // It means that the Apps update is downloaded but not installed. + LOG_INFO << "Apps have been downloaded. Run finalize to install and run them"; + storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kPending); + iresult = {data::ResultCode::Numeric::kNeedCompletion, "finalization must be called to install and start Apps"}; + } else { + LOG_INFO << "Update complete. No reboot needed"; + storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kCurrent); + writeCurrentTarget(target); + updateRequestHeaders(); + } } else if (iresult.result_code.num_code == data::ResultCode::Numeric::kDownloadFailed) { LOG_INFO << "Apps installation failed while the install process was trying to load App images to docker store," " will try the install again at the next update cycle."; diff --git a/src/liteclient.h b/src/liteclient.h index 38e852c2..457fb47b 100644 --- a/src/liteclient.h +++ b/src/liteclient.h @@ -18,6 +18,7 @@ class ReportEvent; class ReportQueue; class DownloadResult; class Downloader; +class Installer; class LiteClient { public: @@ -43,7 +44,7 @@ class LiteClient { bool finalizeInstall(data::InstallationResult* ir = nullptr); Uptane::Target getRollbackTarget(); DownloadResultWithStat download(const Uptane::Target& target, const std::string& reason); - data::ResultCode::Numeric install(const Uptane::Target& target); + data::ResultCode::Numeric install(const Uptane::Target& target, InstallMode install_mode = InstallMode::All); void notifyInstallFinished(const Uptane::Target& t, data::InstallationResult& ir); std::pair isRebootRequired() const { return {is_reboot_required_, config.bootloader.reboot_command}; @@ -91,7 +92,7 @@ class LiteClient { void notifyInstallStarted(const Uptane::Target& t); void writeCurrentTarget(const Uptane::Target& t) const; - data::InstallationResult installPackage(const Uptane::Target& target); + data::InstallationResult installPackage(const Uptane::Target& target, InstallMode install_mode = InstallMode::All); DownloadResultWithStat downloadImage(const Uptane::Target& target, const api::FlowControlToken* token = nullptr); static void add_apps_header(std::vector& headers, PackageConfig& config); data::InstallationResult finalizePendingUpdate(boost::optional& target); @@ -113,6 +114,7 @@ class LiteClient { std::vector no_targets_; std::shared_ptr downloader_; + std::shared_ptr installer_; Json::Value apps_state_; const int report_queue_run_pause_s_{10}; const int report_queue_event_limit_{6}; diff --git a/src/rootfstreemanager.cc b/src/rootfstreemanager.cc index 7026e644..990ee520 100644 --- a/src/rootfstreemanager.cc +++ b/src/rootfstreemanager.cc @@ -168,6 +168,10 @@ void RootfsTreeManager::installNotify(const Uptane::Target& target) { OstreeManager::installNotify(target); } +data::InstallationResult RootfsTreeManager::Install(const TufTarget& target, InstallMode /*mode*/) { + return RootfsTreeManager::install(Target::fromTufTarget(target)); +} + data::InstallationResult RootfsTreeManager::install(const Uptane::Target& target) const { data::InstallationResult res; Uptane::Target current = OstreeManager::getCurrent(); diff --git a/src/rootfstreemanager.h b/src/rootfstreemanager.h index 6344e5dd..0cd2ce06 100644 --- a/src/rootfstreemanager.h +++ b/src/rootfstreemanager.h @@ -4,11 +4,12 @@ #include "bootloader/bootloaderlite.h" #include "downloader.h" #include "http/httpinterface.h" +#include "installer.h" #include "ostree/sysroot.h" #include "package_manager/ostreemanager.h" #include "storage/stat.h" -class RootfsTreeManager : public OstreeManager, public Downloader { +class RootfsTreeManager : public OstreeManager, public Downloader, public Installer { public: static constexpr const char* const Name{"ostree"}; struct Config { @@ -35,17 +36,18 @@ class RootfsTreeManager : public OstreeManager, public Downloader { std::shared_ptr sysroot, const KeyManager& keys); DownloadResultWithStat Download(const TufTarget& target) override; + data::InstallationResult Install(const TufTarget& target, InstallMode mode) override; bool fetchTarget(const Uptane::Target& target, Uptane::Fetcher& fetcher, const KeyManager& keys, const FetcherProgressCb& progress_cb, const api::FlowControlToken* token) override; const bootloader::BootFwUpdateStatus& bootFwUpdateStatus() const { return *boot_fw_update_status_; } void setInitialTargetIfNeeded(const std::string& hw_id); + data::InstallationResult install(const Uptane::Target& target) const override; protected: virtual void completeInitialTarget(Uptane::Target& init_target){}; void installNotify(const Uptane::Target& target) override; - data::InstallationResult install(const Uptane::Target& target) const override; const std::shared_ptr& sysroot() const { return sysroot_; } private: diff --git a/tests/apiclient_test.cc b/tests/apiclient_test.cc index 10c6a111..0c7b023a 100644 --- a/tests/apiclient_test.cc +++ b/tests/apiclient_test.cc @@ -231,6 +231,68 @@ TEST_F(ApiClientTest, InstallWithCorrelationId) { ASSERT_EQ("this-is-random", events[0]["event"]["correlationId"].asString()); } +TEST_F(ApiClientTest, InstallModeOstreeOnlyIfOstreeAndApps) { + auto liteclient = createLiteClient(); + ASSERT_TRUE(targetsMatch(liteclient->getCurrent(), getInitialTarget())); + + std::vector apps{{"app-01", "app-01-URI"}}; + auto new_target = createTarget(&apps); + { + AkliteClient client(liteclient); + + EXPECT_CALL(*getAppEngine(), fetch).Times(1); + // make sure App install is not called + EXPECT_CALL(*getAppEngine(), install).Times(0); + + auto result = client.CheckIn(); + ASSERT_EQ(CheckInResult::Status::Ok, result.status); + + auto latest = result.GetLatest(); + auto installer = client.Installer(latest, "", "", InstallMode::OstreeOnly); + ASSERT_NE(nullptr, installer); + auto dresult = installer->Download(); + ASSERT_EQ(DownloadResult::Status::Ok, dresult.status); + + auto iresult = installer->Install(); + ASSERT_EQ(InstallResult::Status::NeedsCompletion, iresult.status); + reboot(liteclient); + } + { + AkliteClient client(liteclient); + + auto ciresult = client.CompleteInstallation(); + ASSERT_EQ(InstallResult::Status::Ok, ciresult.status); + } +} + +TEST_F(ApiClientTest, InstallModeOstreeOnlyIfJustApps) { + auto liteclient = createLiteClient(); + ASSERT_TRUE(targetsMatch(liteclient->getCurrent(), getInitialTarget())); + + std::vector apps{{"app-01", "app-01-URI"}}; + auto new_target = createAppTarget(apps); + AkliteClient client(liteclient); + + EXPECT_CALL(*getAppEngine(), fetch).Times(1); + // make sure App install is not called + EXPECT_CALL(*getAppEngine(), install).Times(0); + + auto result = client.CheckIn(); + ASSERT_EQ(CheckInResult::Status::Ok, result.status); + + auto latest = result.GetLatest(); + auto installer = client.Installer(latest, "", "", InstallMode::OstreeOnly); + ASSERT_NE(nullptr, installer); + auto dresult = installer->Download(); + ASSERT_EQ(DownloadResult::Status::Ok, dresult.status); + + auto iresult = installer->Install(); + ASSERT_EQ(InstallResult::Status::NeedsCompletion, iresult.status); + + auto ciresult = client.CompleteInstallation(); + ASSERT_EQ(InstallResult::Status::Ok, ciresult.status); +} + TEST_F(ApiClientTest, Secondaries) { AkliteClient client(createLiteClient(InitialVersion::kOff)); std::vector ecus; From 21f25da47df67f0d6121cc37704b26bd26cff62d Mon Sep 17 00:00:00 2001 From: Mike Sul Date: Wed, 15 Nov 2023 14:05:37 +0100 Subject: [PATCH 3/3] cli: Add param to delay App installation - Extend the CLI implementation to support update with delayed Apps installation. - Add a new CLI param to specify the delayed App installation mode. Signed-off-by: Mike Sul --- include/aktualizr-lite/api.h | 4 +++- src/api.cc | 8 ++++++-- src/cli/cli.cc | 14 ++++++++++++-- src/cli/cli.h | 4 +++- src/main.cc | 7 ++++++- tests/apiclient_test.cc | 10 +++++++--- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/include/aktualizr-lite/api.h b/include/aktualizr-lite/api.h index 93007d5c..1573d6d8 100644 --- a/include/aktualizr-lite/api.h +++ b/include/aktualizr-lite/api.h @@ -106,6 +106,7 @@ class InstallResult { Ok = 0, OkBootFwNeedsCompletion, NeedsCompletion, + AppsNeedCompletion, BootFwNeedsCompletion, Failed, DownloadFailed, @@ -116,7 +117,8 @@ class InstallResult { // NOLINTNEXTLINE(hicpp-explicit-conversions,google-explicit-constructor) operator bool() const { - return status == Status::Ok || status == Status::OkBootFwNeedsCompletion || status == Status::NeedsCompletion; + return status == Status::Ok || status == Status::OkBootFwNeedsCompletion || status == Status::NeedsCompletion || + status == Status::AppsNeedCompletion; } }; diff --git a/src/api.cc b/src/api.cc index e73a965e..46eeaf99 100644 --- a/src/api.cc +++ b/src/api.cc @@ -206,7 +206,7 @@ std::string AkliteClient::GetDeviceID() const { return client_->getDeviceID(); } class LiteInstall : public InstallContext { public: LiteInstall(std::shared_ptr client, std::unique_ptr t, std::string& reason, - InstallMode install_mode) + InstallMode install_mode = InstallMode::All) : client_(std::move(client)), target_(std::move(t)), reason_(reason), mode_{install_mode} {} InstallResult Install() override { @@ -216,7 +216,11 @@ class LiteInstall : public InstallContext { auto status = InstallResult::Status::Failed; if (rc == data::ResultCode::Numeric::kNeedCompletion) { if (client_->isPendingTarget(*target_)) { - status = InstallResult::Status::NeedsCompletion; + if (client_->getCurrent().sha256Hash() == target_->sha256Hash()) { + status = InstallResult::Status::AppsNeedCompletion; + } else { + status = InstallResult::Status::NeedsCompletion; + } } else { // If the install returns `kNeedCompletion` and the target being installed is not pending, // then it means that the previous boot fw update requires reboot prior to running the new target update diff --git a/src/cli/cli.cc b/src/cli/cli.cc index b0347887..12adfd83 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -27,11 +27,21 @@ static const std::unordered_map i2s = { {InstallResult::Status::Ok, StatusCode::Ok}, {InstallResult::Status::OkBootFwNeedsCompletion, StatusCode::OkNeedsRebootForBootFw}, {InstallResult::Status::NeedsCompletion, StatusCode::InstallNeedsReboot}, + {InstallResult::Status::AppsNeedCompletion, StatusCode::InstallAppsNeedFinalization}, {InstallResult::Status::BootFwNeedsCompletion, StatusCode::InstallNeedsRebootForBootFw}, {InstallResult::Status::DownloadFailed, StatusCode::InstallAppPullFailure}, }; -StatusCode Install(AkliteClient &client, int version, const std::string &target_name) { +StatusCode Install(AkliteClient &client, int version, const std::string &target_name, const std::string &install_mode) { + const static std::unordered_map str2Mode = {{"delay-app-install", InstallMode::OstreeOnly}}; + InstallMode mode{InstallMode::All}; + if (!install_mode.empty()) { + if (str2Mode.count(install_mode) == 0) { + LOG_WARNING << "Unsupported installation mode: " << install_mode << "; falling back to the default install mode"; + } else { + mode = str2Mode.at(install_mode); + } + } // Check if the device is in a correct state to start a new update if (client.IsInstallationInProgress()) { LOG_ERROR << "Cannot start Target installation since there is ongoing installation; target: " @@ -75,7 +85,7 @@ StatusCode Install(AkliteClient &client, int version, const std::string &target_ LOG_INFO << "To New Target: " << target.Name(); } - const auto installer = client.Installer(target); + const auto installer = client.Installer(target, "", "", mode); if (installer == nullptr) { LOG_ERROR << "Unexpected error: installer couldn't find Target in the DB; try again later"; return StatusCode::UnknownError; diff --git a/src/cli/cli.h b/src/cli/cli.h index ee684676..f8e8507b 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -22,12 +22,14 @@ enum class StatusCode { InstallAppPullFailure = 80, InstallNeedsRebootForBootFw = 90, InstallNeedsReboot = 100, + InstallAppsNeedFinalization = 105, InstallRollbackOk = 110, InstallRollbackNeedsReboot = 120, InstallRollbackFailed = 130, }; -StatusCode Install(AkliteClient &client, int version = -1, const std::string &target_name = ""); +StatusCode Install(AkliteClient &client, int version = -1, const std::string &target_name = "", + const std::string &install_mode = ""); StatusCode CompleteInstall(AkliteClient &client); } // namespace cli diff --git a/src/main.cc b/src/main.cc index f3479cd4..8cf4b036 100644 --- a/src/main.cc +++ b/src/main.cc @@ -435,11 +435,15 @@ static int cli_install(LiteClient& client, const bpo::variables_map& params) { target_name = version_str; } } + std::string install_mode; + if (params.count("install-mode") > 0) { + install_mode = params.at("install-mode").as(); + } std::shared_ptr client_ptr{&client, [](LiteClient* /*unused*/) {}}; AkliteClient akclient{client_ptr, false, true}; - return static_cast(cli::Install(akclient, version, target_name)); + return static_cast(cli::Install(akclient, version, target_name, install_mode)); } static int cli_complete_install(LiteClient& client, const bpo::variables_map& params) { @@ -498,6 +502,7 @@ bpo::variables_map parse_options(int argc, char** argv) { ("ostree-server", bpo::value(), "URL of the Ostree repository") ("primary-ecu-hardware-id", bpo::value(), "hardware ID of primary ecu") ("update-name", bpo::value(), "optional name of the update when running \"update\". default=latest") + ("install-mode", bpo::value(), "Optional install mode. Supported modes: [delay-app-install]. By default both ostree and apps are installed before reboot") #ifdef ALLOW_MANUAL_ROLLBACK ("clear-installed-versions", "DANGER - clear the history of installed updates before applying the given update. This is handy when doing test/debug and you need to rollback to an old version manually.") #endif diff --git a/tests/apiclient_test.cc b/tests/apiclient_test.cc index 0c7b023a..32beae10 100644 --- a/tests/apiclient_test.cc +++ b/tests/apiclient_test.cc @@ -287,10 +287,14 @@ TEST_F(ApiClientTest, InstallModeOstreeOnlyIfJustApps) { ASSERT_EQ(DownloadResult::Status::Ok, dresult.status); auto iresult = installer->Install(); - ASSERT_EQ(InstallResult::Status::NeedsCompletion, iresult.status); + ASSERT_EQ(InstallResult::Status::AppsNeedCompletion, iresult.status); - auto ciresult = client.CompleteInstallation(); - ASSERT_EQ(InstallResult::Status::Ok, ciresult.status); + { + liteclient = createLiteClient(); + AkliteClient client(liteclient); + auto ciresult = client.CompleteInstallation(); + ASSERT_EQ(InstallResult::Status::Ok, ciresult.status); + } } TEST_F(ApiClientTest, Secondaries) {