-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Alberto Planas <aplanas@suse.com>
- Loading branch information
Showing
7 changed files
with
290 additions
and
4 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# Tukit plugins | ||
|
||
## Motivation | ||
|
||
Sometimes it is useful to inspect the content of a transaction in | ||
certain points. For example, before `transactional-update dup` we | ||
want to add new files into the system, and after the update we want to | ||
collect the list of new packages. | ||
|
||
With a plugin system we can provide scripts that can be executed | ||
before or after each action supported by `transactional-update`, like | ||
package installation, creation of new `initrd`, etc. This can work | ||
for most of the use cases, but there are certain tasks that cannot be | ||
done at this level. | ||
|
||
One example of those tasks is inspecting the `/run` directory after a | ||
full system upgrade to look for files that can signalize a condition, | ||
like the one that will trigger the `initrd` creation in a | ||
post-transaction scriptlet. The `/run` directory that is alive inside | ||
the new transaction is different from the one running in the host, but | ||
in both cases it is a `tmpfs` filesystem. This means that every time | ||
that it is unmounted we lost the information stored in there. Sadly, | ||
every time that `transactional-update` calls `tukit` to realize an | ||
action inside the snapshot, those directories are mounted and | ||
unmounted. | ||
|
||
The solution for this is to have the plugins at the `tukit` level. | ||
|
||
## Directories and shadowing | ||
|
||
The plugins can be installed in two different places: | ||
|
||
* `/usr/lib/tukit/plugins`: place for plugins that comes from packages | ||
* `/etc/tukit/plugins`: for user defined plugins | ||
|
||
The plugins in `/etc` can shadow the ones from `/usr` using the same | ||
name. For example, if the plugin `get_status` is in both places with | ||
the executable attribute, `tukit` will use the code from `/etc`, | ||
shadowing the one from the packages. | ||
|
||
One variation of shadowing is when the plugin in `/etc` is a soft link | ||
to `/dev/null`. This will be used as a mark to completely disable | ||
this plugin, and would not be called by `tukit`. | ||
|
||
The plugins in `/etc` will be called before the ones in `/usr` but the | ||
user should not depend on the calling order. | ||
|
||
## Stages | ||
|
||
The actions are based on the low-level API of libtukit, not of the | ||
user API level. This means that some verbs like `tukit execute <cmd>` | ||
will be presented as several of those low level actions: create | ||
snapshot, execute command, keep snapshot. | ||
|
||
Some actions will trigger a plugin call before and after the action | ||
itself, depending if it makes sense in the context of this action. | ||
|
||
The next table summarizes the action, the stage and different | ||
parameters sent to the plugin. | ||
|
||
| Action | Stage | Parameters | Notes | | ||
|----------|-------|-----------------------------------|-------| | ||
| init | -pre | | | | ||
| | -post | path, snapshot\_id | | | ||
| resume | -pre | snapshot\_id | | | ||
| | -post | path, snapshot\_id | | | ||
| execute | -pre | path, snapshot\_id, action params | | | ||
| | -post | path, snapshot\_id, action params | | | ||
| callExt | -pre | path, snapshot\_id, action params | [1] | | ||
| | -post | path, snapshot\_id, action params | | | ||
| finalize | -pre | path, snapshot\_id | | | ||
| | -post | snapshot\_id, [discarded] | [2] | | ||
| abort | -post | snapshot\_id | [3] | | ||
| keep | -pre | path, snapshot\_id | | | ||
| | -post | snapshot\_id | | | ||
| reboot | -pre | | | | ||
|
||
[1] The {} placeholder gets expanded in the arguments passed to the | ||
plugin | ||
|
||
[2] If the snapshot is discarded, the second parameter for the -post | ||
is "discarded" | ||
|
||
[3] abort-pre cannot be captured from the libtukit level | ||
|
||
|
||
## Example | ||
|
||
```bash | ||
#!/bin/bash | ||
|
||
exec_pre() { | ||
local path="$1"; shift | ||
local snapshot_id="$1"; shift | ||
local cmd="$@" | ||
|
||
# The live snapshot is in "$path", and the future closed snapshot in | ||
# "/.snapshots/${snapshot_id}/snapshot | ||
|
||
mkdir -p /var/lib/report | ||
echo "${snapshot_id}: $cmd" >> /var/lib/report/all_commands | ||
} | ||
|
||
declare -A commands | ||
|
||
commands['execute-pre']=exec_pre | ||
commands['callExt-pre']=exec_pre | ||
|
||
cmd="$1" | ||
shift | ||
[ -n "$cmd" ] || cmd=help | ||
if [ "${#commands[$cmd]}" -gt 0 ]; then | ||
${commands[$cmd]} "$@" | ||
fi | ||
``` |
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,91 @@ | ||
/* SPDX-License-Identifier: LGPL-2.1-or-later */ | ||
/* SPDX-FileCopyrightText: 2024 SUSE LLC */ | ||
|
||
/* Plugin mechanism for tukit */ | ||
|
||
#include "Exceptions.hpp" | ||
#include "Log.hpp" | ||
#include "Plugins.hpp" | ||
#include "Util.hpp" | ||
#include <set> | ||
#include <unistd.h> | ||
|
||
namespace TransactionalUpdate { | ||
|
||
using namespace std; | ||
|
||
Plugins::Plugins(TransactionalUpdate::Transaction* transaction): transaction(transaction) { | ||
set<string> plugins_set{}; | ||
|
||
const filesystem::path plugins_dir{filesystem::path(CONFDIR)/"tukit"/"plugins"}; | ||
const filesystem::path system_plugins_dir{filesystem::path(PREFIX)/"lib"/"tukit"/"plugins"}; | ||
|
||
for (auto d: {plugins_dir, system_plugins_dir}) { | ||
if (!filesystem::exists(d)) | ||
continue; | ||
|
||
for (auto const& dir_entry: filesystem::directory_iterator{d}) { | ||
auto path = dir_entry.path(); | ||
auto filename = dir_entry.path().filename(); | ||
|
||
// Plugins can be shadowed, so a plugin in /etc can | ||
// replace one from /usr/lib | ||
if (plugins_set.count(filename) != 0) | ||
continue; | ||
|
||
// If is a symlink to /dev/null, ignore and shadow it | ||
if (filesystem::is_symlink(path) && filesystem::read_symlink(path) == "/dev/null") { | ||
plugins_set.insert(filename); | ||
continue; | ||
} | ||
|
||
// If the plugin is not executable, ignore it | ||
if (!(filesystem::is_regular_file(path) && (access(path.c_str(), X_OK) == 0))) | ||
continue; | ||
|
||
tulog.info("Found plugin ", path); | ||
plugins.push_back(path); | ||
plugins_set.insert(filename); | ||
} | ||
} | ||
} | ||
|
||
Plugins::~Plugins() { | ||
plugins.clear(); | ||
} | ||
|
||
void Plugins::run(string stage, string args) { | ||
std::string output; | ||
|
||
for (auto& p: plugins) { | ||
std::string cmd = p.string() + " " + stage; | ||
if (!args.empty()) | ||
cmd.append(" " + args); | ||
|
||
try { | ||
output = Util::exec(cmd); | ||
if (!output.empty()) | ||
tulog.info("Output of plugin ", p, ": ", output); | ||
} catch (const ExecutionException &e) { | ||
// An error in the plugin should not discard the transaction | ||
tulog.error("ERROR: Plugin ", p, " failed with ", e.what()); | ||
} | ||
} | ||
} | ||
|
||
void Plugins::run(string stage, char* argv[]) { | ||
std::string args; | ||
|
||
if (transaction != nullptr) | ||
args.append(transaction->getBindDir().string() + " " + transaction->getSnapshot()); | ||
|
||
int i = 0; | ||
while (argv != nullptr && argv[i]) { | ||
args.append(" "); | ||
args.append(argv[i++]); | ||
} | ||
|
||
run(stage, args); | ||
} | ||
|
||
} // namespace TransactionalUpdate |
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 @@ | ||
/* SPDX-License-Identifier: LGPL-2.1-or-later */ | ||
/* SPDX-FileCopyrightText: 2024 SUSE LLC */ | ||
|
||
/* Plugin mechanism for tukit */ | ||
|
||
#ifndef T_U_PLUGINS_H | ||
#define T_U_PLUGINS_H | ||
|
||
#include "Transaction.hpp" | ||
#include <filesystem> | ||
#include <string> | ||
#include <vector> | ||
|
||
namespace TransactionalUpdate { | ||
|
||
class Plugins { | ||
public: | ||
Plugins(TransactionalUpdate::Transaction* transaction); | ||
virtual ~Plugins(); | ||
void run(std::string stage, std::string args); | ||
void run(std::string stage, char* argv[]); | ||
protected: | ||
TransactionalUpdate::Transaction* transaction; | ||
std::vector<std::filesystem::path> plugins; | ||
}; | ||
|
||
} // namespace TransactionalUpdate | ||
|
||
#endif // T_U_PLUGINS_H |
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
Oops, something went wrong.