Skip to content
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
15 changes: 10 additions & 5 deletions .devcontainer/opensuse/Containerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# Build stage with Spack pre-installed and ready to be used
FROM docker.io/opensuse/leap:15.5 AS builder

RUN zypper install -y --no-recommends \
curl git python3-pip python3-devel gcc14 gcc14-c++ xz patch gpg2 patchelf tar gzip unzip bzip2 \
wget autoconf automake zlib-devel libcurl-devel cmake3 \
libboost_filesystem1_75_0-devel libboost_regex1_75_0-devel libboost_program_options1_75_0-devel \
&& pip3 install clingo gcovr
RUN zypper ref && zypper -n install --no-recommends \
curl git python311 python311-pip python311-devel \
gcc14 gcc14-c++ xz patch gpg2 patchelf tar gzip unzip bzip2 \
wget autoconf automake zlib-devel libcurl-devel cmake3 \
libboost_filesystem1_75_0-devel libboost_regex1_75_0-devel libboost_program_options1_75_0-devel \
libxml2-devel libxslt-devel \
&& update-alternatives --set python3 /usr/bin/python3.11 || true \
&& python3.11 -m pip install --upgrade "pip<25" setuptools wheel \
&& python3.11 -m pip install clingo gcovr

ENV CC=/usr/bin/gcc-14
ENV CXX=/usr/bin/g++-14

Expand Down
103 changes: 103 additions & 0 deletions .github/workflows/ci-container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: Build & Publish CI Container

on:
push:
paths:
- '.devcontainer/opensuse/Containerfile'

jobs:
changes:
runs-on: ubuntu-latest
outputs:
ci_changed: ${{ steps.filter.outputs.ci }}
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Detect CI‐container changes
id: filter
uses: dorny/paths-filter@v3
with:
filters: |
ci:
- '.devcontainer/opensuse/Containerfile'

build-and-push-arch:
needs: changes
if: ${{ needs.changes.outputs.ci_changed == 'true' }}
strategy:
matrix:
include:
- arch: amd64
runner: ubuntu-latest
platform: "linux/amd64"
- arch: arm64
runner: ubuntu-24.04-arm
platform: "linux/arm64"
runs-on: ${{ matrix.runner }}

permissions:
contents: read
packages: write

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build image
uses: docker/build-push-action@v6
with:
context: .devcontainer/opensuse
file: .devcontainer/opensuse/Containerfile
platforms: ${{ matrix.platform }}
push: true
tags: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/ci-runner:${{ matrix.arch }}-${{ github.sha }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha,scope=ci-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=ci-${{ matrix.arch }}

# single multi-arch
publish:
needs: [build-and-push-arch, changes]
if: ${{ github.event_name != 'pull_request' && needs.changes.outputs.ci_changed == 'true' }}
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
env:
IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/ci-runner
SHA: ${{ github.sha }}
steps:
- name: setup Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create multi-arch manifest (with :latest and :$SHA)
run: |
docker buildx imagetools create \
-t "${IMAGE}:latest" \
-t "${IMAGE}:${SHA}" \
"${IMAGE}:amd64-${SHA}" \
"${IMAGE}:arm64-${SHA}"

- name: Check manifest
run: docker buildx imagetools inspect "${IMAGE}:${SHA}"

75 changes: 75 additions & 0 deletions .github/workflows/z-test-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Run on Zinal

on:
push:
branches:
- 'fc-*'
- 'LdCacheHook'

jobs:
build:
if: github.actor == 'fcruzcscs'
runs-on: zinal
steps:
- name: Setup temporary dir
run: |
TMP_DIR=$(mktemp -d)
echo "TMP_DIR=$TMP_DIR" >> $GITHUB_ENV
echo "Using temp directory: $TMP_DIR"

- name: Manually clone repo within tmp dir
run: git clone --depth 1 --branch "${GITHUB_REF_NAME}" https://github.com/${GITHUB_REPOSITORY}.git
working-directory: ${{ env.TMP_DIR }}

- name: List contents of TMP_DIR
run: ls -la
working-directory: ${{ env.TMP_DIR }}

- name: Run build inside Podman container
run: |
podman run --rm \
-v "$PWD":"$PWD":Z \
-v "$TMP_DIR":"$TMP_DIR":Z \
-w "$PWD" \
--env TMP_DIR="$TMP_DIR" \
--env GITHUB_REF_NAME="$GITHUB_REF_NAME" \
ghcr.io/sarus-suite/sarus-hooks/ci-runner:latest \
bash -euxc '
mkdir -p $TMP_DIR/opt
cd $TMP_DIR/opt
git clone --recursive https://github.com/sarus-suite/libsarus.git
cd libsarus

# Building libsarus to std system prefix
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ..
make -j
make install

# Building hooks
cd $TMP_DIR/sarus-hooks
## ensure dependencies are pulled
git submodule update --init --recursive --depth 1


cmake -S . -B build \
-DBUILD_DROPBEAR=0 \
-DENABLE_UNIT_TESTS=0 \
-DCMAKE_INSTALL_PREFIX=$TMP_DIR/podman-hooks
cmake --build build --parallel
cmake --install build
'
working-directory: ${{ env.TMP_DIR }}/sarus-hooks

- name: Install Bats locally
run: |
git clone --depth 1 https://github.com/bats-core/bats-core.git "$TMP_DIR/bats-core"
"$TMP_DIR/bats-core/install.sh" "$TMP_DIR/bats"
working-directory: ${{ env.TMP_DIR }}

- name: Testing podman run
run: |
export PODMAN_BINARY=$(which podman)
$PODMAN_BINARY version
working-directory: ${{ env.TMP_DIR}}

1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_subdirectory(ssh)
add_subdirectory(slurm_global_sync)
add_subdirectory(timestamp)
add_subdirectory(stdout_stderr_test)
add_subdirectory(ldcache)

# Collective target for hooks
add_custom_target(hooks ALL)
Expand Down
12 changes: 12 additions & 0 deletions src/ldcache/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
file(GLOB hooks_ldcache_srcs "*.cpp" "*.c")
list(REMOVE_ITEM hooks_ldcache_srcs ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
add_library(hooks_ldcache_library STATIC ${hooks_ldcache_srcs})
target_link_libraries(hooks_ldcache_library sarus ${Boost_LIBRARIES})

add_executable(ldcache_hook "main.cpp")
target_link_libraries(ldcache_hook hooks_ldcache_library)
install(TARGETS ldcache_hook DESTINATION ${CMAKE_INSTALL_PREFIX}/bin PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)

57 changes: 57 additions & 0 deletions src/ldcache/LdCacheHook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "LdCacheHook.hpp"

#include "libsarus/Error.hpp"
#include "libsarus/Utility.hpp"

namespace sarus {
namespace hooks {
namespace ldcache {

void LdCacheHook::activate() {
log("Activating hook", libsarus::LogLevel::INFO);

containerState = libsarus::hook::parseStateOfContainerFromStdin();

parseConfigJSONOfBundle(); // get rootfsDir
parseEnvironmentVariables(); // get ldconfigPath

if (!ldconfigPath.empty()) {
log("Updating dynamic linker cache", libsarus::LogLevel::INFO);

libsarus::process::executeCommand(ldconfigPath.string() + " -v -r " + rootfsDir.string());
}

log("Successful hook", libsarus::LogLevel::INFO);
}

void LdCacheHook::parseConfigJSONOfBundle() {
log("Parsing JSON of bundle", libsarus::LogLevel::INFO);

auto json = libsarus::json::read(containerState.bundle() / "config.json");
libsarus::hook::applyLoggingConfigIfAvailable(json);

auto root = boost::filesystem::path{ json["root"]["path"].GetString() };
rootfsDir = root.is_absolute() ? root : (containerState.bundle() / root);

log("Success parsing config.json", libsarus::LogLevel::INFO);
}

void LdCacheHook::parseEnvironmentVariables() {
log("Parsing environment variables", libsarus::LogLevel::INFO);

try {
ldconfigPath = libsarus::environment::getVariable("LDCONFIG_PATH");

log("Success parsing LDCONFIG_PATH", libsarus::LogLevel::INFO);
}
catch (const libsarus::Error&) {
log("LDCONFIG_PATH not set. Using default ldconfig", libsarus::LogLevel::INFO);
ldconfigPath = "ldconfig";
}
}

void LdCacheHook::log(const std::string &msg, libsarus::LogLevel lvl) const{
libsarus::Logger::getInstance().log(msg, "ldcache-hook", lvl);
}

}}} // closing namespaces
31 changes: 31 additions & 0 deletions src/ldcache/LdCacheHook.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef sarus_hooks_ldcache_LdCacheHook_hpp
#define sarus_hooks_ldcache_LdCacheHook_hpp

#include <string>
#include <boost/filesystem.hpp>

#include "libsarus/Utility.hpp"
#include "libsarus/Logger.hpp"

namespace sarus {
namespace hooks {
namespace ldcache {

class LdCacheHook {
public:
void activate();

private:
void parseConfigJSONOfBundle();
void parseEnvironmentVariables();
void log(const std::string& msg, libsarus::LogLevel lvl) const;

private:
libsarus::hook::ContainerState containerState;
boost::filesystem::path rootfsDir;
boost::filesystem::path ldconfigPath;
};

}}} // closing namespaces

#endif
4 changes: 4 additions & 0 deletions src/ldcache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# ldcache Hook for Sarus Suite

During the creation of a container (createContainer stage), this hook enters the container rootfs and runs 'ldconfig -v -r <rootfs>' so the container cache ('/etc/ld.so.cache') is refreshed using the libraries already installed in the image. This hook is minimal and safe to operate: no extra mounts or edits beyond the normal operation of 'ldconfig'.

14 changes: 14 additions & 0 deletions src/ldcache/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include "LdCacheHook.hpp"
#include "libsarus/Error.hpp"
#include "libsarus/Logger.hpp"

int main(int argc, char* argv[]){
try {
sarus::hooks::ldcache::LdCacheHook{}.activate();
}
catch (const libsarus::Error &e) {
libsarus::Logger::getInstance().logErrorTrace(e, "ldcache-hook");
exit(EXIT_FAILURE);
}
return 0;
}
51 changes: 51 additions & 0 deletions tests/test_ldcache_hook.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
setup() {
RUNTIME="${RUNTIME:-crun}"
TEST_IMAGE="${TEST_IMAGE:-ubuntu:20.04}"

TMP_HOOKS_DIR="$(mktemp -d)"
TMP_HOOK_LOG_DIR="$(mktemp -d)"
HOOK_OUT="${TMP_HOOK_LOG_DIR}/out.log"
HOOK_ERR="${TMP_HOOK_LOG_DIR}/err.log"

cat > "${TMP_HOOKS_DIR}/02-ldcache.json" <<'EOF'
{
"version": "1.0.0",
"hook": {
"path": "/opt/sarus/bin/ldcache_hook",
"env": ["LDCONFIG_PATH=/sbin/ldconfig"]
},
"stages": ["createContainer"]
}
EOF
}

teardown() {
rm -rf "${TMP_HOOKS_DIR}" "${TMP_HOOK_LOG_DIR}"
}

helper_run_hooked_podman() {
# cleanup hook output
: > "${HOOK_OUT}"
: > "${HOOK_ERR}"

# run hook with debug output
podman --runtime="${RUNTIME}" \
--hooks-dir "${TMP_HOOKS_DIR}" \
run --rm \
--annotation com.hooks.logging.level=0 \
--annotation run.oci.hooks.stdout="${HOOK_OUT}" \
--annotation run.oci.hooks.stderr="${HOOK_ERR}" \
"${TEST_IMAGE}" bash -lc "$1"
}

@test "hook runs and generates debug logs" {
run helper_run_hooked_podman 'true'
[ "$status" -eq 0 ]
# [ -s "$HOOK_OUT" ]
}

#@test "/etc/ld.so.cache exists after hook" {
# # TODO: can we validate that ld.so.cache was recently updated? idea: inject a lib and then see it in hook log
# run helper_run_hooked_podman 'true'
# [ "$status" -eq 0 ]
#}