Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/cloysterhpc/services/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct Options final {
bool runAsDaemon;
bool airGap;
bool unattended;
bool disableMirrors;
bool enableMirrors;
std::size_t logLevelInput;
std::string error;
std::string config;
Expand Down
108 changes: 85 additions & 23 deletions include/cloysterhpc/services/runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <boost/process.hpp>
#include <fmt/format.h>

#include <stdexcept>
#include <string>
#include <vector>

Expand All @@ -17,35 +18,96 @@
#include <cloysterhpc/services/options.h>
#include <cloysterhpc/services/scriptbuilder.h>

namespace cloyster::services::runner {
namespace cloyster::services::runner::shell {

namespace unsafe {
template <typename... Args>
[[nodiscard]]
int fmt(std::vector<std::string>& output,
fmt::format_string<Args...> format, Args&&... args)
{
auto command = fmt::format(format, std::forward<Args>(args)...);

auto opts = cloyster::Singleton<cloyster::services::Options>::get();
if (!opts->dryRun) {
LOG_DEBUG("Running shell command: {}", command);
boost::process::ipstream pipe_stream;
boost::process::child child("/bin/bash", "-c", command,
boost::process::std_out > pipe_stream);

std::string line;
while (pipe_stream && std::getline(pipe_stream, line)) {
output.emplace_back(line);
LOG_TRACE("{}", line);
}

child.wait();
LOG_DEBUG("Exit code: {}", child.exit_code());
return child.exit_code();
} else {
LOG_INFO("Dry Run: {}", command);
return 0;
}
}

template <typename... Args>
int shellfmt(fmt::format_string<Args...> fmt, Args&&... args)
{
auto command = fmt::format(fmt, std::forward<Args>(args)...);

auto opts = cloyster::Singleton<cloyster::services::Options>::get();
if (!opts->dryRun) {
LOG_DEBUG("Running shell command: {}", command);
boost::process::ipstream pipe_stream;
boost::process::child child(
"/bin/bash", "-c", command, boost::process::std_out > pipe_stream);

std::string line;
while (pipe_stream && std::getline(pipe_stream, line)) {
LOG_TRACE("{}", line);
template <typename... Args>
[[nodiscard]]
int fmt(fmt::format_string<Args...> format, Args&&... args)
{
auto command = fmt::format(format, std::forward<Args>(args)...);

auto opts = cloyster::Singleton<cloyster::services::Options>::get();
if (!opts->dryRun) {
LOG_DEBUG("Running shell command: {}", command);
boost::process::ipstream pipe_stream;
boost::process::child child("/bin/bash", "-c", command,
boost::process::std_out > pipe_stream);

std::string line;
while (pipe_stream && std::getline(pipe_stream, line)) {
LOG_TRACE("{}", line);
}

child.wait();
LOG_DEBUG("Exit code: {}", child.exit_code());
return child.exit_code();
} else {
LOG_INFO("Dry Run: {}", command);
return 0;
}
}
}

child.wait();
LOG_DEBUG("Exit code: {}", child.exit_code());
return child.exit_code();
} else {
LOG_INFO("Dry Run: {}", command);
return 0;
template <typename... Args>
void fmt(fmt::format_string<Args...> format, Args&&... args)
{
const std::string command
= fmt::format(format, std::forward<Args>(args)...);
const auto exitCode = unsafe::fmt("{}", command);
if (exitCode != 0) {
throw std::runtime_error(fmt::format(
"Command {} failed with exit code {}", command, exitCode));
}
}

int shell(std::string_view cmd);
void cmd(std::string_view cmd);

template <typename... Args>
[[nodiscard]]
std::string output(fmt::format_string<Args...> format, Args&&... args)
{
std::vector<std::string> output;
// Cosntruct a command here because it will be used twice and
// we can't use args twice without hurting the perfect forwarding
const std::string command
= fmt::format(format, std::forward<Args>(args)...);
const auto exitCode = unsafe::fmt(output, "{}", command);
if (exitCode != 0) {
throw std::runtime_error(fmt::format(
"Command {} failed with exit code {}", command, exitCode));
}
return fmt::format("{}", fmt::join(output, "\n"));
}

}

Expand Down
3 changes: 2 additions & 1 deletion repos/repos.conf
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ filename=xcat-deps.repo
upstream.repo=http://xcat.org/files/xcat/repos/yum/devel/xcat-dep/rh{releasever}/{arch}/
upstream.gpgkey=http://xcat.org/files/xcat/repos/yum/devel/xcat-dep/rh{releasever}/{arch}/repodata/repomd.xml.key

# ofedVersion is configured in the answerfile at ofed.version
[doca]
name=NVIDIA DOCA Repository - RHEL rhel{osversion}
filename=mlx-doca.repo
upstream.repo=https://linux.mellanox.com/public/repo/doca/latest/rhel{osversion}/{arch}/
upstream.repo=https://linux.mellanox.com/public/repo/doca/{ofedVersion}/rhel{osversion}/{arch}/
upstream.gpgkey=https://linux.mellanox.com/public/repo/doca/latest/rhel{osversion}/{arch}/GPG-KEY-Mellanox.pub
6 changes: 5 additions & 1 deletion rpmspecs/opencattus.spec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Name: opencattus-installer
Version: 1.0
Release: 2
Release: 4
Summary: OpenCATTUS Installer
License: Apache 2.0
URL: https://versatushpc.com.br/opencattus/
Expand Down Expand Up @@ -49,6 +49,10 @@ install -m 644 repos/rocky-vault.conf %{buildroot}/opt/cloysterhpc/conf/repos/ro
/opt/cloysterhpc/conf/repos/rocky-vault.conf

%changelog
* Thu Aug 14 2025 Daniel Hilst <daniel@versatushpc.com.br> - 1.0-4 - Bugfix
- Update OFED
- Dump configuration
- Add support for Rocky Linux 9.6
* Wed Jul 16 2025 Daniel Hilst <daniel@versatushpc.com.br> - 1.0-3 - Add ansible roles
- Add ansible roles implementation
- Fix dnssec configuration generation in xCAT plugin
Expand Down
43 changes: 28 additions & 15 deletions src/diskImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,43 @@ void DiskImage::setPath(const std::filesystem::path& path)

bool DiskImage::isKnownImage(const std::filesystem::path& path)
{
constexpr auto chooseDistro = [](std::string_view imageView)
-> std::optional<cloyster::models::OS::Distro> {
if (imageView.starts_with("Rocky")) {
return cloyster::models::OS::Distro::Rocky;
} else if (imageView.starts_with("rhel")) {
return cloyster::models::OS::Distro::RHEL;
} else if (imageView.starts_with("OracleLinux")) {
return cloyster::models::OS::Distro::OL;
} else if (imageView.starts_with("AlmaLinux")) {
return cloyster::models::OS::Distro::AlmaLinux;
} else {
return std::nullopt;
}
};

for (const auto& image : m_knownImageFilename) {
if (path.filename().string() == image) {
LOG_TRACE("Disk image is recognized")

auto imageView = std::string_view(image);
if (imageView.starts_with("Rocky")) {
m_distro = cloyster::models::OS::Distro::Rocky;
} else if (imageView.starts_with("rhel")) {
m_distro = cloyster::models::OS::Distro::RHEL;
} else if (imageView.starts_with("OracleLinux")) {
m_distro = cloyster::models::OS::Distro::OL;
} else if (imageView.starts_with("AlmaLinux")) {
m_distro = cloyster::models::OS::Distro::AlmaLinux;
} else {
throw std::logic_error(fmt::format(
"Can't determine the distro for the image {}", image));
const auto distro = chooseDistro(imageView);
if (distro) {
m_distro = distro;
return true;
}

return true;
}
}

LOG_TRACE("Disk image is unknown. Maybe you're using a custom image or "
"changed the default name?");
const auto distro
= chooseDistro(std::string_view(path.filename().string()));
if (distro) {
m_distro = distro;
return true;
}
cloyster::functions::abort(
"Disk image is unknown. Maybe you're using a custom image or "
"changed the default name?");
return false;
}

Expand Down
21 changes: 20 additions & 1 deletion src/models/answerfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/lexical_cast.hpp>
#include <chrono>
#include <cloysterhpc/functions.h>
#include <cloysterhpc/models/answerfile.h>
#include <cloysterhpc/services/log.h>
#include <cloysterhpc/services/options.h>
#include <cloysterhpc/services/osservice.h>
#include <cstddef>
#include <fmt/core.h>
#include <iterator>
Expand Down Expand Up @@ -426,7 +428,24 @@ void AnswerFile::loadSystemSettings()
}

system.version = m_keyfile.getString("system", "version");
system.kernel = m_keyfile.getString("system", "kernel");
const auto kernel = m_keyfile.getStringOpt("system", "kernel");
if (kernel) {
system.kernel = kernel.value();
LOG_INFO("Kernel override in the answerfile {}", system.kernel);
} else {
const auto latestKernelVersion = services::runner::shell::output(
// This runs very early so it stops loading all repositories caches,
// which is unecessary, so I pinned --repo=appstream here
"dnf list --repo=appstream kernel-devel --available "
"--showduplicates | sed 1d | "
// captures the kernel arch, e.g. x86_64, in $1
// this is important later when used to access /lib/modules/...
// folders
R"(perl -lane '$F[0] =~ s/kernel-devel\.(.*)$//; printf "%s.%s\n", $F[1], $1' | tail -1)");
system.kernel = latestKernelVersion;
LOG_INFO("Kernel omitted in the answerfile, using running kernel {}",
system.kernel);
}
}

AFNode AnswerFile::loadNode(const std::string& section)
Expand Down
21 changes: 13 additions & 8 deletions src/ofed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

#include <fmt/core.h>

#include <cloysterhpc/cloyster.h>
#include <cloysterhpc/functions.h>
#include <cloysterhpc/ofed.h>
Expand Down Expand Up @@ -47,6 +49,8 @@ bool OFED::installed() const
void OFED::install() const
{
const auto opts = cloyster::Singleton<cloyster::services::Options>::get();
const auto cluster = cloyster::Singleton<cloyster::models::Cluster>::get();
const auto osinfo = cluster->getNodes()[0].getOS();

if (opts->dryRun) {
LOG_WARN("Dry-Run: Skiping OFED installation");
Expand All @@ -73,14 +77,16 @@ void OFED::install() const
cloyster::services::repos::RepoManager>::get();
auto osService
= cloyster::Singleton<cloyster::services::IOSService>::get();
const std::string kernelVersion = std::string(osinfo.getKernel());
repoManager->enable("doca");
// Install the required packages
runner->checkCommand("dnf makecache --repo=doca");
runner->checkCommand(
"dnf -y install kernel kernel-devel doca-extra");
fmt::format("dnf -y install kernel-{kernelVersion} "
"kernel-devel-{kernelVersion} doca-extra",
fmt::arg("kernelVersion", kernelVersion)));

if (osService->getKernelRunning()
!= osService->getKernelInstalled()) {
if (osService->getKernelRunning() != kernelVersion) {
LOG_WARN("New kernel installed! Rebooting after the "
"installation finishes is advised!");
}
Expand All @@ -96,10 +102,9 @@ void OFED::install() const
// The driver may support weak updates modules and load without
// need for reboot.
if (!opts->shouldSkip("compile-doca-driver")) {
runner->checkCommand(
"bash -c \"/opt/mellanox/doca/tools/doca-kernel-support -k "
"$(rpm -q --qf \"%{VERSION}-%{RELEASE}.%{ARCH}\n\" "
"kernel-devel)\"");
runner->checkCommand(fmt::format(
"/opt/mellanox/doca/tools/doca-kernel-support -k {}",
kernelVersion));
}

// Get the last rpm in /tmp/DOCA*/ folder
Expand All @@ -123,4 +128,4 @@ void OFED::install() const

break;
}
}
}
14 changes: 12 additions & 2 deletions src/services/ansible/roles/base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,18 @@ ScriptBuilder installScript(

builder.addNewLine().addCommand("# Install general base packages");

std::set<std::string> allPackages = { "wget", "curl", "dnf-plugins-core",
"chkconfig", "jq", "tar", "python3-dnf-plugin-versionlock" };
// "python3-dnf-plugin-versionlock" is conflicting with dnf-plugins-core
// during the first install
std::set<std::string> allPackages = {
"wget",
"curl",
"dnf-plugins-core",
"chkconfig",
"initscripts", // @FIXME: This is only required if the provisioner is
// xCAT
"jq",
"tar",
};
if (const auto iter = role.m_vars.find("base_packages");
iter != role.m_vars.end()) {
for (const auto& pkg :
Expand Down
2 changes: 2 additions & 0 deletions src/services/ansible/roles/spack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ ScriptBuilder installScript(
fmt::format("Expected spack role, found {}", role.m_roleName));

builder.addNewLine()
.addCommand("# Exit early if spack is already installed")
.addCommand("test -d /opt/spack/.git && exit 0")
.addCommand("# Install dependencies for Spack")
.addPackage("git")
.addNewLine()
Expand Down
10 changes: 10 additions & 0 deletions src/services/ansible/roles/todo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Base (open)
1. confluent
2. RHEL10 sup

Features (closed)
1. ood
2. grafana
3. slurm
4. integração ood slurm
5. lustre
5 changes: 2 additions & 3 deletions src/services/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ std::unique_ptr<Options> options::factory(int argc, const char** argv)
.runAsDaemon = false,
.airGap = false,
.unattended = false,
.disableMirrors = false,
.enableMirrors = false,
.logLevelInput = 3,
.error = "NO ERROR",
.config = "",
Expand All @@ -47,8 +47,7 @@ std::unique_ptr<Options> options::factory(int argc, const char** argv)
app.add_flag("-t,--tui", opt.enableTUI, "Enable TUI");
app.add_flag("-c,--cli", opt.enableCLI, "Enable CLI");
app.add_flag("-D,--daemon", opt.runAsDaemon, "Run as daemon");
app.add_flag(
"--disable-mirrors", opt.disableMirrors, "Disable mirror URLs");
app.add_flag("--enable-mirrors", opt.enableMirrors, "Disable mirror URLs");
app.add_option("--mirror-url", opt.mirrorBaseUrl, "Base URL for mirror")
->default_str("https://mirror.versatushpc.com.br");
app.add_option(
Expand Down
Loading
Loading