Skip to content

Commit

Permalink
Updates for Public Builds (#4)
Browse files Browse the repository at this point in the history
* Update edit mode to use ro on actual spfs mount rather than runtime

This provides more consistent behaviour between older and newer kernels
and overlayfs versions

* Update rpm to build within a custom docker build

* Fix test that failed when no config file is installed

* Add docs for getting started and local development

* Add initial workflow for rust build and test

* Add pipeline to build and test rpm package

* Update readme with other docs links
  • Loading branch information
rydrman committed Apr 16, 2021
1 parent 92fdc13 commit 0c6f77a
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 64 deletions.
4 changes: 1 addition & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
.git/
.mypy_cache/
__pycache__/
.pytest_cache/
target/
.vscode/
prof/
36 changes: 36 additions & 0 deletions .github/workflows/rpm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: RPM Build

on:
pull_request:
branches: [master]
push:
branches: [master]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: echo RPM_NAME=$(rpmspec -q spfs.spec | head -n1) >> $GITHUB_ENV
- run: echo Building ${{ env.RPM_NAME }}.rpm
- name: build RPM package
run: make rpm
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: Binary RPM
path: dist/rpm/RPMS/x86_64/${{ env.RPM_NAME }}.rpm
test:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- run: echo RPM_NAME=$(rpmspec -q spfs.spec | head -n1) >> $GITHUB_ENV
- uses: actions/download-artifact@v2
with:
name: Binary RPM
- run: docker run --privileged --rm
-v $PWD/$RPM_NAME.rpm:/tmp/$RPM_NAME.rpm
-v $PWD/tests/integration:/tests
centos:7
bash -xc "yum install -y /tmp/$RPM_NAME.rpm && bash /tests/run_tests.sh"
21 changes: 21 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Rust

on:
pull_request:
branches: [master]
push:
branches: [master]

env:
CARGO_TERM_COLOR: always

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: sudo apt-get install -y libcap-dev
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
23 changes: 23 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
VERSION = $(shell cat spfs.spec | grep Version | cut -d ' ' -f 2)
SOURCE_ROOT := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))

.PHONY: rpm debug test
default: debug

debug:
cd $(SOURCE_ROOT)
cargo build

test:
cargo test

rpm:
cd $(SOURCE_ROOT)
docker build . \
-f rpmbuild.Dockerfile \
--build-arg VERSION=$(VERSION) \
--tag spfs-rpm-builder
mkdir -p dist/rpm
CONTAINER=$$(docker create spfs-rpm-builder) \
&& docker cp $$CONTAINER:/root/rpmbuild/RPMS dist/rpm/ \
&& docker rm --force $$CONTAINER
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
# spfs

Filesystem isolation, capture, and distribution.

Additional information is available under [docs](docs/).

## Development

For local development, some tests will require the privileged binary to be built and have its capabilities set. You can rely on the system install of spfs for this in most cases, or run the `build.sh` script with sudo if you need to validate changes to the `spfs-enter` binary itself.
SpFS is written in Rust and uses Cargo. The best way to get started with rust development is to install the latest stable rust toolchain using [rustup](https://rustup.sh). More detailed design docs are available under [docs/design](docs/design/).

### Building

Once setup with Rust, building and running a local debug build of spfs is as easy as:

```sh
cargo build
target/debug/spfs --help
```

### Binaries and Capabilities

Spfs builds into a number of separate binaries, all of which can be run through the main `spfs` binary. Some of these binaries require special capabilities to be set in order to function properly. The `setcaps_debug.sh` script can be used to set these capabilities on your locally-compiled debug binaries.

```sh
sudo setcaps_debug.sh
```

### RPM Package

The spfs codebase is setup to produce a centos7-compatible rpm package by building spfs in a docker container. To create the rpm package, you will need docker installed. These packages are also built and made available in this repository's CI.

```sh
# build the rpm package via docker and copy into ./dist/rpm
make rpm
```

### Testing

`./build_rpm.sh` is the most consistent way to build the rpm file, which can easily be `sudo yum install`'d into the current system for validation.
Spfs has a number of unit tests written in rust that can be run using the `cargo` command.

The `build.sh` script compiles the binaries and standalone binary file into a local build folder.
```sh
cargo test
```

For python development, however, `pipenv shell` followed by calls to `pytest` and `python -m spfs ...` are the simplest and fastest.
Additionally, there are a number of integration tests that validate the fully installed state of spfs. These are generally a series of spfs command line calls that validate the creation and usage of the `/spfs` filesystem.

`Nuitka` is used to compile the codebase and all necessary dependencies into a standalone binary file for distributions. This adds optimizations to the code, and stops the resulting binary from being environment-dependant.
```sh
cargo build
./setcaps_debug.sh
tests/integration/run_all.sh
```
27 changes: 27 additions & 0 deletions rpmbuild.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM centos:7
ARG VERSION

RUN yum install -y \
curl \
rpm-build \
&& yum clean all

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh /dev/stdin -y
ENV PATH $PATH:/root/.cargo/bin

RUN mkdir -p /root/rpmbuild/{SOURCES,SPECS,RPMS,SRPMS}

COPY spfs.spec /root/rpmbuild/SPECS/
ENV VERSION ${VERSION}
RUN echo "Building for $VERSION"

# ensure the current build version matches the one in the rpm
# spec file, or things can go awry
RUN test "$VERSION" == "$(cat /root/rpmbuild/SPECS/spfs.spec | grep Version | cut -d ' ' -f 2)"

RUN yum-builddep -y /root/rpmbuild/SPECS/spfs.spec && yum clean all

COPY . /source/spfs-$VERSION
RUN tar -C /source -czvf /root/rpmbuild/SOURCES/v$VERSION.tar.gz .

RUN rpmbuild -ba /root/rpmbuild/SPECS/spfs.spec
15 changes: 15 additions & 0 deletions setcaps_debug.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/bash
# Sets required capabilities on the local debug builds of spfs.
# Must be run as root, and ./target dir must be on a local filesystem (not NFS)

if [ "$EUID" -ne 0 ]
then echo "Must be run as root, re-run with sudo"
exit
fi

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

cmds=$(cat spfs.spec | grep '%caps' | sed -r 's|%caps\((.*)\) (.*)|setcap \1 \2|' | sed "s|/usr/bin/|$DIR/target/debug/|")

set -ex
$cmds
17 changes: 8 additions & 9 deletions spfs.spec
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ Version: 0.27.0
Release: 1
Summary: Filesystem isolation, capture, and distribution.
License: NONE
URL: https://gitlab.spimageworks.com/dev-group/dev-ops/spfs
Source0: https://gitlab.spimageworks.com/dev-group/dev-ops/spfs/-/archive/v%{version}/%{name}-v%{version}.tar.gz
URL: https://github.com/imageworks/spfs
Source0: https://github.com/imageworks/spfs/archive/refs/tags/v%{version}.tar.gz


Requires: expect >= 5, expect < 6
BuildRequires: rsync
Expand All @@ -13,27 +14,25 @@ BuildRequires: gcc-c++
BuildRequires: chrpath
BuildRequires: libcap-devel
BuildRequires: openssl-devel
BuildRequires: spdev

%define debug_package %{nil}

%description
Filesystem isolation, capture, and distribution.

%prep
%setup -q -n %{name}-v%{version}
%setup -q

%build
dev toolchain install
source ~/.bashrc
dev env -- dev build spfs
cargo build --release --verbose

%install
mkdir -p %{buildroot}/usr/bin
for cmd in build/spfs/release/spfs build/spfs/release/spfs-*; do
RELEASE_DIR=%{_builddir}/%{name}-%{version}/target/release
for cmd in $RELEASE_DIR/spfs $RELEASE_DIR/spfs-*; do
# skip debug info for commands
if [[ $cmd =~ \.d$ ]]; then continue; fi
install -p -m 755 %{_builddir}/%{name}-v%{version}/$cmd %{buildroot}/usr/bin/
install -p -m 755 $cmd %{buildroot}/usr/bin/
done

%files
Expand Down
2 changes: 1 addition & 1 deletion src/cli/cmd_enter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl CmdEnter {
pub fn run(&mut self, config: &spfs::Config) -> spfs::Result<i32> {
let runtime = spfs::runtime::Runtime::new(&self.runtime_root)?;
if self.remount {
spfs::reinitialize_runtime(&runtime, &config)?;
spfs::reinitialize_runtime(&runtime)?;
Ok(0)
} else {
let cmd = match self.cmd.take() {
Expand Down
38 changes: 8 additions & 30 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,15 @@ pub fn get_overlay_args<P: AsRef<Path>>(lowerdirs: impl IntoIterator<Item = P>)
Ok(args)
}

pub fn mount_env<P: AsRef<Path>>(lowerdirs: impl IntoIterator<Item = P>) -> Result<()> {
pub fn mount_env<P: AsRef<Path>>(
editable: bool,
lowerdirs: impl IntoIterator<Item = P>,
) -> Result<()> {
tracing::debug!("mounting the overlay filesystem...");
let overlay_args = get_overlay_args(lowerdirs)?;
let mut overlay_args = get_overlay_args(lowerdirs)?;
if !editable {
overlay_args = format!("ro,{}", overlay_args);
}
tracing::debug!(
"/usr/bin/mount -t overlay -o {} none {}",
overlay_args,
Expand Down Expand Up @@ -347,34 +353,6 @@ pub fn unmount_env() -> Result<()> {
Ok(())
}

pub fn unlock_runtime(tmpfs_opts: Option<&str>) -> Result<()> {
use nix::mount::{mount, MsFlags};
let result = mount(
NONE,
RUNTIME_DIR,
Some("tmpfs"),
MsFlags::MS_REMOUNT,
tmpfs_opts,
);
if let Err(err) = result {
return Err(Error::wrap_nix(err, "Failed to unlock runtime"));
}
Ok(())
}

pub fn set_runtime_lock(editable: bool, tmpfs_opts: Option<&str>) -> Result<()> {
use nix::mount::{mount, MsFlags};
let mut flags = MsFlags::MS_REMOUNT;
if !editable {
flags |= MsFlags::MS_RDONLY;
}
let result = mount(NONE, RUNTIME_DIR, Some("tmpfs"), flags, tmpfs_opts);
if let Err(err) = result {
return Err(Error::wrap_nix(err, "Failed to set runtime lock"));
}
Ok(())
}

pub fn become_original_user(uids: Uids) -> Result<()> {
tracing::debug!("dropping root...");
let mut result = nix::unistd::setuid(uids.uid);
Expand Down
16 changes: 3 additions & 13 deletions src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,16 @@ pub fn active_runtime() -> Result<runtime::Runtime> {
}

/// Reinitialize the current spfs runtime as rt (in case of runtime config changes).
pub fn reinitialize_runtime(rt: &runtime::Runtime, config: &Config) -> Result<()> {
pub fn reinitialize_runtime(rt: &runtime::Runtime) -> Result<()> {
let dirs = resolve_overlay_dirs(&rt)?;
tracing::debug!("computing runtime manifest");
let manifest = compute_runtime_manifest(&rt)?;

let tmpfs_opts = config
.filesystem
.tmpfs_size
.as_ref()
.map(|size| format!("size={}", size));

let original = env::become_root()?;
env::ensure_mounts_already_exist()?;
env::unmount_env()?;
env::setup_runtime()?;
env::unlock_runtime(tmpfs_opts.as_ref().map(|s| s.as_str()))?;
env::mount_env(&dirs)?;
env::mount_env(rt.is_editable(), &dirs)?;
env::mask_files(&manifest, original.uid)?;
env::set_runtime_lock(rt.is_editable(), None)?;
env::become_original_user(original)?;
env::drop_all_capabilities()?;
Ok(())
Expand All @@ -124,9 +115,8 @@ pub fn initialize_runtime(rt: &runtime::Runtime, config: &Config) -> Result<()>
env::ensure_mount_targets_exist()?;
env::mount_runtime(tmpfs_opts.as_ref().map(|s| s.as_str()))?;
env::setup_runtime()?;
env::mount_env(&dirs)?;
env::mount_env(rt.is_editable(), &dirs)?;
env::mask_files(&manifest, original.uid)?;
env::set_runtime_lock(rt.is_editable(), None)?;
env::become_original_user(original)?;
env::drop_all_capabilities()?;
Ok(())
Expand Down
13 changes: 10 additions & 3 deletions src/sync_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,22 @@ use storage::RepositoryHandle;
fixtures!();

#[rstest]
fn test_push_ref_unknown() {
fn test_push_ref_unknown(config: (tempdir::TempDir, Config)) {
let _guard = init_logging();
match push_ref("--test-unknown--", None) {
let (_handle, config) = config;
match push_ref(
"--test-unknown--",
Some(config.get_remote("origin").unwrap()),
) {
Err(Error::UnknownReference(_)) => (),
Err(err) => panic!("expected unknown reference error, got {:?}", err),
Ok(_) => panic!("expected unknown reference error, got success"),
}

match push_ref(encoding::Digest::default().to_string(), None) {
match push_ref(
encoding::Digest::default().to_string(),
Some(config.get_remote("origin").unwrap()),
) {
Err(Error::UnknownObject(_)) => (),
Err(err) => panic!("expected unknown reference error, got {:?}", err),
Ok(_) => panic!("expected unknown reference error, got success"),
Expand Down
Empty file modified tests/integration/run_tests.sh
100644 → 100755
Empty file.

0 comments on commit 0c6f77a

Please sign in to comment.