FlatImage
A configurable Linux containerization system
Loading...
Searching...
No Matches
recipe.hpp
Go to the documentation of this file.
1
8
9#pragma once
10
11#include <filesystem>
12#include <fstream>
13#include <unordered_set>
14#include <string>
15
17#include "../../lib/log.hpp"
20#include "../../db/recipe.hpp"
21#include "../../config.hpp"
22#include "desktop.hpp"
23
24namespace
25{
26
27namespace fs = std::filesystem;
28
37inline fs::path get_path_recipe(
38 fs::path const& path_dir_download
39 , ns_config::Distribution const& distribution
40 , std::string const& recipe
41)
42{
43 return path_dir_download / "recipes" / distribution.lower() / "latest" / std::format("{}.json", recipe);
44}
45
46} // namespace
47
57namespace ns_recipe
58{
59
69 ns_config::Distribution const& distribution
70 , fs::path const& path_dir_download
71 , std::string const& recipe
72)
73{
74 // Construct path to local recipe file
75 fs::path recipe_file = get_path_recipe(path_dir_download, distribution, recipe);
76 // Check if recipe file exists locally
77 return_if(not fs::exists(recipe_file),
78 Error("E::Recipe '{}' not found locally. Use 'fim-recipe fetch {}' first.", recipe, recipe));
79 // Read the local JSON file
80 std::ifstream file(recipe_file);
81 return_if(!file.is_open(),
82 Error("E::Could not open recipe file '{}'", recipe_file.string()));
83 // Read json contents
84 std::string json_contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
85 // Check if contents are empty
86 return_if(json_contents.empty(), Error("E::Empty json file"));
87 // Parse JSON and return
88 return Pop(ns_db::ns_recipe::deserialize(json_contents), "E::Could not parse json file");
89}
90
103[[nodiscard]] inline Value<void> fetch_impl(
104 ns_config::Distribution const& distribution
105 , std::string url_remote
106 , fs::path const& path_file_downloader
107 , fs::path const& path_dir_download
108 , std::string const& recipe
109 , bool use_existing
110 , std::unordered_set<std::string>& dependencies
111)
112{
113 auto f_fetch_dependencies = [&](ns_db::ns_recipe::Recipe const& recipe_obj) -> Value<void>
114 {
115 auto const& deps = recipe_obj.get_dependencies();
116 if (!deps.empty())
117 {
118 for (auto const& sub_recipe : deps)
119 {
120 Pop(fetch_impl(distribution, url_remote, path_file_downloader, path_dir_download, sub_recipe, use_existing, dependencies));
121 }
122 }
123 return {};
124 };
125 // Check cycle
126 if(dependencies.contains(recipe))
127 {
128 return Error("E::Cyclic dependency for recipe '{}'", recipe);
129 }
130 else
131 {
132 dependencies.insert(recipe);
133 }
134 // Construct the output path
135 fs::path path_file_output = get_path_recipe(path_dir_download, distribution, recipe);
136 fs::path path_dir_output = path_file_output.parent_path();
137 // If use_existing is true and file exists, use the cached version
138 if (use_existing && Try(fs::exists(path_file_output)))
139 {
140 logger("I::Using existing recipe from '{}'", path_file_output.string());
141 // Read the local JSON file
142 std::ifstream file(path_file_output);
143 if (!file.is_open())
144 {
145 return Error("E::Could not open existing recipe file '{}'", path_file_output.string());
146 }
147 // Read file contents
148 std::string json_contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
149 file.close();
150 // Parse JSON
151 ns_db::ns_recipe::Recipe recipe_obj = Pop(ns_db::ns_recipe::deserialize(json_contents));
152 // Fetch dependencies recursively if they exist
153 Pop(f_fetch_dependencies(recipe_obj));
154 return {};
155 }
156 // Download the recipe (either use_existing=false or file doesn't exist)
157 // Remove trailing slash from URL if present
158 if (url_remote.ends_with('/'))
159 {
160 url_remote.pop_back();
161 }
162 // Construct the recipe URL: URL/DISTRO/VERSION/<recipe>.json
163 std::string recipe_url = std::format("{}/{}/latest/{}.json", url_remote, distribution.lower(), recipe);
164 // Create the output directory if it doesn't exist
165 Pop(ns_fs::create_directories(path_dir_output));
166 // Download the recipe using wget
167 logger("I::Downloading recipe from '{}'", recipe_url);
168 logger("I::Saving to '{}'", path_file_output.string());
169 // Execute wget to download the file
170 Pop(ns_subprocess::Subprocess(path_file_downloader)
171 .with_args("-O", path_file_output.string(), recipe_url)
172 .spawn()->wait());
173 logger("I::Successfully downloaded recipe '{}' to '{}'", recipe, path_file_output.string());
174 // Read the downloaded JSON file
175 std::ifstream file(path_file_output);
176 if (!file.is_open())
177 {
178 return Error("E::Could not open downloaded recipe file '{}'", path_file_output.string());
179 }
180 // Read file contents
181 std::string json_contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
182 file.close();
183 // Parse JSON
184 ns_db::ns_recipe::Recipe recipe_obj = Pop(ns_db::ns_recipe::deserialize(json_contents));
185 // Fetch dependencies recursively if they exist
186 Pop(f_fetch_dependencies(recipe_obj));
187 return {};
188}
189
202 ns_config::Distribution const& distribution
203 , std::string url_remote
204 , fs::path const& path_file_downloder
205 , fs::path const& path_dir_download
206 , std::string const& recipe
207 , bool use_existing = false
208)
209{
210 std::unordered_set<std::string> dependencies;
211 Pop(fetch_impl(distribution, url_remote, path_file_downloder, path_dir_download, recipe, use_existing, dependencies));
212 return std::vector(dependencies.begin(), dependencies.end());
213}
214
223[[nodiscard]] inline Value<void> info(
224 ns_config::Distribution const& distribution
225 , fs::path const& path_dir_download
226 , std::string const& recipe
227)
228{
229 // Load recipe
230 ns_db::ns_recipe::Recipe recipe_obj = Pop(load_recipe(distribution, path_dir_download, recipe));
231 // Display recipe information
232 fs::path recipe_file = get_path_recipe(path_dir_download, distribution, recipe);
233 std::println("Recipe: {}", recipe);
234 std::println("Location: {}", recipe_file.string());
235 // Description
236 std::println("Description: {}", recipe_obj.get_description());
237 // Packages
238 auto const& packages = recipe_obj.get_packages();
239 std::println("Package count: {}", packages.size());
240 std::println("Packages:");
241 for (auto const& pkg : packages)
242 {
243 std::println(" - {}", pkg);
244 }
245 // Dependencies
246 auto const& dependencies = recipe_obj.get_dependencies();
247 if (!dependencies.empty())
248 {
249 std::println("Dependencies: {}", dependencies.size());
250 for (auto const& dep : dependencies)
251 {
252 std::println(" - {}", dep);
253 }
254 }
255 else
256 {
257 std::println("Dependencies: 0");
258 }
259 return {};
260}
261
273template<typename F>
274requires std::invocable<F,std::string,std::vector<std::string>&>
275[[nodiscard]] inline Value<int> install(ns_config::FlatImage const& fim
276 , ns_config::Distribution const& distribution
277 , fs::path const& path_dir_download
278 , std::vector<std::string> const& recipes
279 , F&& callback)
280{
281
282 // Get packages from recipes and find desktop integration data
283 std::vector<std::string> packages;
284 Value<ns_db::ns_desktop::Desktop> desktop = std::unexpected("No desktop integration found");
285 for(auto&& recipe : recipes)
286 {
287 // Load recipe
288 ns_db::ns_recipe::Recipe recipe_obj = Pop(load_recipe(distribution, path_dir_download, recipe), "E::Could not load json recipe");
289 // Extract and collect packages
290 auto const& recipe_packages = recipe_obj.get_packages();
291 std::ranges::copy(recipe_packages, std::back_inserter(packages));
292 // Check for desktop integration data (use last one found)
293 if(recipe_obj.get_desktop())
294 {
295 desktop = recipe_obj.get_desktop();
296 logger("I::Found desktop integration in recipe '{}'", recipe);
297 }
298 }
299 // Determine package manager command based on distribution
300 std::string program;
301 std::vector<std::string> args;
302 switch(distribution)
303 {
304 case ns_config::Distribution::ALPINE:
305 {
306 program = "apk";
307 args = {"add", "--no-cache", "--update-cache", "--no-progress"};
308 }
309 break;
310 case ns_config::Distribution::ARCH:
311 {
312 program = "pacman";
313 args = {"-Syu", "--noconfirm", "--needed"};
314 }
315 break;
316 case ns_config::Distribution::BLUEPRINT:
317 {
318 return Error("E::Blueprint does not support recipes");
319 }
320 break;
321 case ns_config::Distribution::NONE:
322 {
323 return Error("E::Unsupported distribution '{}' for recipe installation", distribution);
324 }
325 }
326 // Copy packages to arguments
327 std::ranges::copy(packages, std::back_inserter(args));
328 // Execute package manager using the provided callback
329 int exit_code = Pop(callback(program, args));
330 // Setup desktop integration if found
331 if(desktop)
332 {
333 logger("I::Setting up desktop integration from recipe");
334 // Serialize desktop object to JSON and write to temporary file
335 std::string desktop_json = Pop(ns_db::ns_desktop::serialize(Pop(desktop)));
336 fs::path path_file_desktop_json = Try(fs::temp_directory_path()) / "recipe_desktop.json";
337 std::ofstream file_desktop(path_file_desktop_json, std::ios::out | std::ios::trunc);
338 return_if(not file_desktop.is_open(), Error("E::Could not create temporary desktop JSON file"));
339 file_desktop << desktop_json;
340 file_desktop.close();
341 // Setup desktop integration using the temporary JSON file
342 if(auto ret = ns_desktop::setup(fim, path_file_desktop_json); not ret)
343 {
344 logger("W::Failed to setup desktop integration: {}", ret.error());
345 }
346 else
347 {
348 logger("I::Desktop integration setup successful");
349 }
350 // Clean up temporary file
351 Try(fs::remove(path_file_desktop_json));
352 }
353 return exit_code;
354}
355
356} // namespace ns_recipe
357
358/* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
std::unique_ptr< Child > spawn()
Spawns (forks) the child process and begins execution.
FlatImage configuration object.
Defines a class that manages FlatImage's recipe configuration.
Enhanced error handling framework built on std::expected.
A library for file logging.
#define logger(fmt,...)
Compile-time log level dispatch macro with automatic location capture.
Definition log.hpp:682
fs::path get_path_recipe(fs::path const &path_dir_download, ns_config::Distribution const &distribution, std::string const &recipe)
Constructs the path to a recipe file in the local cache.
Definition recipe.hpp:37
Value< std::string > serialize(Desktop const &desktop) noexcept
Serializes a Desktop class into a json string.
Definition desktop.hpp:109
Value< Recipe > deserialize(std::string_view str_raw_json) noexcept
Deserializes a string into a Recipe class.
Definition recipe.hpp:67
Value< void > setup(ns_config::FlatImage const &fim, fs::path const &path_file_json_src)
Setup desktop integration in FlatImage.
Definition desktop.hpp:563
Value< fs::path > create_directories(fs::path const &p)
Creates directories recursively.
Package recipe command implementation.
Value< void > info(ns_config::Distribution const &distribution, fs::path const &path_dir_download, std::string const &recipe)
Displays information about a locally cached recipe.
Definition recipe.hpp:223
Value< ns_db::ns_recipe::Recipe > load_recipe(ns_config::Distribution const &distribution, fs::path const &path_dir_download, std::string const &recipe)
Loads a recipe from local cache.
Definition recipe.hpp:68
Value< int > install(ns_config::FlatImage const &fim, ns_config::Distribution const &distribution, fs::path const &path_dir_download, std::vector< std::string > const &recipes, F &&callback)
Installs packages from recipes using the appropriate package manager.
Definition recipe.hpp:275
Value< std::vector< std::string > > fetch(ns_config::Distribution const &distribution, std::string url_remote, fs::path const &path_file_downloder, fs::path const &path_dir_download, std::string const &recipe, bool use_existing=false)
Fetches a recipe from the remote repository along with all its dependencies recursively.
Definition recipe.hpp:201
Value< void > fetch_impl(ns_config::Distribution const &distribution, std::string url_remote, fs::path const &path_file_downloader, fs::path const &path_dir_download, std::string const &recipe, bool use_existing, std::unordered_set< std::string > &dependencies)
Internal implementation that fetches a recipe and its dependencies recursively with cycle detection.
Definition recipe.hpp:103
Manages the desktop integration.
Filesystem helpers.
Enhanced expected type with integrated logging capabilities.
Definition expected.hpp:44
Main FlatImage configuration object.
Definition config.hpp:519
A library to spawn sub-processes in linux.