Skip to content

Commit

Permalink
initial NixOS integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
aksiksi committed Nov 16, 2023
1 parent 0dfca11 commit 137c6fb
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 5 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/nixos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Reference: https://github.com/skatolo/gh-actions-nixos-tests
name: NixOS

on:
push:
paths-ignore:
- '**/*.md'
branches:
- main
pull_request:
paths-ignore:
- '**/*.md'

jobs:
nixos-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
with:
go-version: '>=1.21'
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: cachix/install-nix-action@v22
with:
extra_nix_config: "system-features = nixos-test benchmark big-parallel kvm"
github_access_token: ${{ secrets.GITHUB_TOKEN }}
nix_path: nixpkgs=channel:nixos-unstable
- uses: DeterminateSystems/magic-nix-cache-action@main
# This brings up two NixOS VMs - one for Docker and one for Podman - and ensures that
# the compose2nix generated config works when loaded into NixOS.
- name: NixOS test
run: |
sudo chmod o+rw /dev/kvm
./nixos-test/run.sh
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ jobs:
with:
fail_ci_if_error: true
files: coverage.out
check-nix-flake:
linux-build-flake:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v22
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
nix_path: nixpkgs=channel:nixos-unstable
- run: nix build
- run: nix flake check
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: nix build -L .#packages.x86_64-linux.default
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
bin/
docker-compose.yml
docker-compose.nix
/docker-compose.yml
/docker-compose.nix
out.nix*
*.env
result
14 changes: 14 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,19 @@
buildInputs = [ pkgs.go pkgs.gopls ];
};
});

# Run:
# nix build .#checks.x86_64-linux.integrationTest
# To run interactively:
# nix build .#checks.x86_64-linux.integrationTest.driverInteractive
# See: https://nixos.org/manual/nixos/stable/index.html#sec-running-nixos-tests-interactively
checks.x86_64-linux = let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in {
# This test is meant to be run by nixos-test/test.sh.
# https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests
# https://nix.dev/tutorials/nixos/integration-testing-using-virtual-machines
integrationTest = pkgs.nixosTest (import ./nixos-test/test.nix);
};
};
}
90 changes: 90 additions & 0 deletions nixos-test/docker-compose.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Auto-generated using compose2nix v0.1.4.
{ pkgs, lib, ... }:

{
# Runtime
virtualisation.docker = {
enable = true;
autoPrune.enable = true;
};
virtualisation.oci-containers.backend = "docker";

# Containers
virtualisation.oci-containers.containers."myproject-alpine" = {
image = "docker.io/alpine:latest";
environment = {
TZ = "America/New_York";
};
volumes = [
"/var/volumes/alpine:/config:rw"
"storage:/storage:rw"
];
log-driver = "journald";
extraOptions = [
"--network-alias=alpine"
"--network=myproject-default"
];
};
systemd.services."docker-myproject-alpine" = {
serviceConfig = {
Restart = lib.mkOverride 500 "always";
RuntimeMaxSec = lib.mkOverride 500 360;
};
unitConfig = {
Description = lib.mkOverride 500 "This is the alpine container!";
};
after = [
"docker-network-myproject-default.service"
"docker-volume-storage.service"
];
requires = [
"docker-network-myproject-default.service"
"docker-volume-storage.service"
];
partOf = [
"docker-compose-myproject-root.target"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
};

# Networks
systemd.services."docker-network-myproject-default" = {
path = [ pkgs.docker ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "${pkgs.docker}/bin/docker network rm -f myproject-default";
};
script = ''
docker network inspect myproject-default || docker network create myproject-default
'';
partOf = [ "docker-compose-myproject-root.target" ];
wantedBy = [ "docker-compose-myproject-root.target" ];
};

# Volumes
systemd.services."docker-volume-storage" = {
path = [ pkgs.docker ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
docker volume inspect storage || docker volume create storage --opt=device=/mnt/media --opt=o=bind --opt=type=none
'';
partOf = [ "docker-compose-myproject-root.target" ];
wantedBy = [ "docker-compose-myproject-root.target" ];
};

# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."docker-compose-myproject-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
}
33 changes: 33 additions & 0 deletions nixos-test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: "3.7"
name: "myproject"
services:
alpine:
image: docker.io/alpine:latest
environment:
TZ: ${TIMEZONE}
volumes:
- /var/volumes/alpine:/config
- storage:/storage
labels:
- "compose2nix.systemd.service.RuntimeMaxSec=360"
- "compose2nix.systemd.unit.Description=This is the alpine container!"
restart: unless-stopped

networks:
something:
labels:
- "test-label=okay"

volumes:
storage:
name: storage
driver_opts:
type: none
device: /mnt/media
o: bind
books:
name: books
driver_opts:
type: none
device: /mnt/media/Books
o: bind
79 changes: 79 additions & 0 deletions nixos-test/podman-compose.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Auto-generated using compose2nix v0.1.4.
{ pkgs, lib, ... }:

{
# Runtime
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
defaultNetwork.settings = {
# Required for container networking to be able to use names.
dns_enabled = true;
};
};
virtualisation.oci-containers.backend = "podman";

# Containers
virtualisation.oci-containers.containers."myproject-alpine" = {
image = "docker.io/alpine:latest";
environment = {
TZ = "America/New_York";
};
volumes = [
"/mnt/media:/storage:rw"
"/var/volumes/alpine:/config:rw"
];
log-driver = "journald";
extraOptions = [
"--network-alias=alpine"
"--network=myproject-default"
];
};
systemd.services."podman-myproject-alpine" = {
serviceConfig = {
Restart = lib.mkOverride 500 "always";
RuntimeMaxSec = lib.mkOverride 500 360;
};
unitConfig = {
Description = lib.mkOverride 500 "This is the alpine container!";
};
after = [
"podman-network-myproject-default.service"
];
requires = [
"podman-network-myproject-default.service"
];
partOf = [
"podman-compose-myproject-root.target"
];
wantedBy = [
"podman-compose-myproject-root.target"
];
};

# Networks
systemd.services."podman-network-myproject-default" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "${pkgs.podman}/bin/podman network rm -f myproject-default";
};
script = ''
podman network inspect myproject-default || podman network create myproject-default --opt isolate=true
'';
partOf = [ "podman-compose-myproject-root.target" ];
wantedBy = [ "podman-compose-myproject-root.target" ];
};

# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-myproject-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
}
3 changes: 3 additions & 0 deletions nixos-test/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
./nixos-test/update.sh
nix build -L .#checks.x86_64-linux.integrationTest --option sandbox false
55 changes: 55 additions & 0 deletions nixos-test/test.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{ ... }:

{
name = "basic";
nodes = {
docker = { pkgs, ... }: {
imports = [
./docker-compose.nix
];
virtualisation.graphics = false;
system.stateVersion = "23.05";
};
podman = { pkgs, ... }: {
imports = [
./podman-compose.nix
];
virtualisation.graphics = false;
system.stateVersion = "23.05";
};
};
skipLint = true;
# TODO(aksiksi): This currently takes way too long to pull the images.
# Perhaps we need to build and use local images?
testScript = ''
import time
def num_running_containers() -> int:
stdout = m.execute(f"{runtime} ps --format '{{.ID}}: {{.Names}} - {{.State}}' | grep running | wc -l")[1]
return int(stdout.strip())
start_all()
d = {"docker": docker, "podman": podman}
for runtime, m in d.items():
# Create required directories for Docker Compose volumes and bind mounts.
m.execute("mkdir -p /mnt/media/Books")
m.execute("mkdir -p /var/volumes/alpine")
# Wait for root Compose service to come up.
m.wait_for_unit(f"{runtime}-compose-myproject-root.target")
# Wait for container services.
m.wait_for_unit(f"{runtime}-myproject-alpine.service")
# Poll container state.
num_attempts = 0
while num_attempts < 20:
if num_running_containers() == 1:
break
num_attempts += 1
time.sleep(3)
else:
raise Exception("timeout!")
'';
}
14 changes: 14 additions & 0 deletions nixos-test/update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

export TIMEZONE="America/New_York"

# Generate NixOS configs for each runtime.
make build
bin/compose2nix \
-inputs=nixos-test/docker-compose.yml \
-output=nixos-test/docker-compose.nix \
-runtime=docker
bin/compose2nix \
-inputs=nixos-test/docker-compose.yml \
-output=nixos-test/podman-compose.nix \
-runtime=podman

0 comments on commit 137c6fb

Please sign in to comment.