Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for NixOS containers #10

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions build.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
, enableTomcatWebApplication ? false
, enableMongoDatabase ? false
, enableNginxWebApplication ? false
, enableNixosContainer ? false
, enableSubversionRepository ? false
, enableInfluxDatabase ? false
, enableSupervisordProgram ? false
Expand Down Expand Up @@ -33,6 +34,7 @@ pkgs.releaseTools.nixBuild {
(if enablePostgreSQLDatabase then "--with-postgresql" else "--without-postgresql")
(if enableMongoDatabase then "--with-mongodb" else "--without-mongodb")
(if enableNginxWebApplication then "--with-nginx" else "--without-nginx")
(if enableNixosContainer then "--with-nixos-container" else "--without-nixos-container")
(if enableTomcatWebApplication then "--with-tomcat=${catalinaBaseDir}" else "--without-tomcat")
(if enableSubversionRepository then "--with-subversion" else "--without-subversion")
(if enableInfluxDatabase then "--with-influxdb" else "--without-influxdb")
Expand All @@ -52,6 +54,7 @@ pkgs.releaseTools.nixBuild {
++ pkgs.lib.optional enableMongoDatabase pkgs.mongodb
++ pkgs.lib.optional enableMongoDatabase pkgs.mongodb-tools
++ pkgs.lib.optional enableNginxWebApplication pkgs.nginx
++ pkgs.lib.optional enableNixosContainer pkgs.nixos-container
++ pkgs.lib.optional enableSubversionRepository pkgs.subversion
++ pkgs.lib.optional enableInfluxDatabase pkgs.influxdb
++ pkgs.lib.optional enableSystemdUnit pkgs.systemd
Expand Down
9 changes: 9 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ AC_ARG_WITH([mysql], AS_HELP_STRING([--without-mysql], [Ignore presence of MySQL
AC_ARG_WITH([postgresql], AS_HELP_STRING([--without-postgresql], [Ignore presence of PostgreSQL and disable it]))
AC_ARG_WITH([mongodb], AS_HELP_STRING([--without-mongodb], [Ignore presence of MongoDB and disable it]))
AC_ARG_WITH([nginx], AS_HELP_STRING([--without-nginx], [Ignore presence of Nginx and disable it]))
AC_ARG_WITH([nixos-container], AS_HELP_STRING([--without-nixos-container], [Ignore presence of support for NixOS containers]))
AC_ARG_WITH([influxdb], AS_HELP_STRING([--without-influxdb], [Ignore presence of InfluxDB and disable it]))
AC_ARG_WITH([tomcat], AS_HELP_STRING([--with-tomcat], [Enable Apache Tomcat module and specifies location of the Apache Tomcat base directory]))
AC_ARG_WITH([subversion], AS_HELP_STRING([--with-subversion], [Enable subversion repository module]))
Expand Down Expand Up @@ -94,6 +95,9 @@ AS_IF([test "x$with_nginx" != "x" && test "x$with_nginx" != "xno"],
[have_nginx=yes],
[have_nginx=no])

AS_IF([test "x$with_nixos_container" != "xno"],
[AC_PATH_PROG(nixos_container_bin, nixos-container)])

AS_IF([test "x$with_postgresql" != "xno"],
[AC_PATH_PROG(createdb, createdb)])

Expand Down Expand Up @@ -194,6 +198,7 @@ AM_CONDITIONAL(have_mysql, [test x$mysql != "x"])
AM_CONDITIONAL(have_postgresql, [test x$psql != "x"])
AM_CONDITIONAL(have_mongodb, [test x$mongo != "x"])
AM_CONDITIONAL(have_nginx, [test x$have_nginx != "xno"])
AM_CONDITIONAL(have_nixos_container, [test x$nixos_container_bin != "x"])
AM_CONDITIONAL(have_influxdb, [test x$influx != "x"])
AM_CONDITIONAL(have_tomcat, [test x$have_tomcat != "xno"])
AM_CONDITIONAL(have_subversion, [test x$svnadmin != "x"])
Expand Down Expand Up @@ -241,6 +246,9 @@ AS_IF([test "x$mongo" != "x"],
AS_IF([test "x$have_nginx" != "xno"],
[nginx_webapplication=dysnomia-modules/nginx-webapplication])

AS_IF([test "x$nixos_container_bin" != "x"],
[nixos_container=dysnomia-modules/nixos-container])

AS_IF([test "x$influx" != "x"],
[influx_database=dysnomia-modules/influx-database])

Expand Down Expand Up @@ -321,6 +329,7 @@ $mysql_database
$postgresql_database
$mongo_database
$nginx_webapplication
$nixos_container
$influx_database
$tomcat_webapplication
$subversion_repository
Expand Down
2 changes: 2 additions & 0 deletions dysnomia-module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ let
enableDockerContainer = config.virtualisation.docker.enable;
enableEjabberdDump = config.services.ejabberd.enable;
enableMySQLDatabase = config.services.mysql.enable;
enableNixosContainer = config.boot.enableContainers;
enablePostgreSQLDatabase = config.services.postgresql.enable;
enableTomcatWebApplication = config.services.tomcat.enable;
enableMongoDatabase = config.services.mongodb.enable;
Expand Down Expand Up @@ -183,6 +184,7 @@ in
++ optional (dysnomiaFlags.enableEjabberdDump) "ejabberd-dump"
++ optional (dysnomiaFlags.enableInfluxDatabase) "influx-database"
++ optional (dysnomiaFlags.enableMySQLDatabase) "mysql-database"
++ optional (dysnomiaFlags.enableNixosContainer) "nixos-container"
++ optional (dysnomiaFlags.enablePostgreSQLDatabase) "postgresql-database"
++ optional (dysnomiaFlags.enableTomcatWebApplication) "tomcat-webapplication"
++ optional (dysnomiaFlags.enableMongoDatabase) "mongo-database"
Expand Down
5 changes: 5 additions & 0 deletions dysnomia-modules/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ if have_nginx
libexec_SCRIPTS += nginx-webapplication
endif

if have_nixos_container
libexec_SCRIPTS += nixos-container
endif

if have_postgresql
libexec_SCRIPTS += postgresql-database
endif
Expand Down Expand Up @@ -116,6 +120,7 @@ EXTRA_DIST = apache-webapplication.in \
mysql-database.in \
nginx-webapplication.in \
nixos-configuration.in \
nixos-container.in \
postgresql-database.in \
process.in \
s6-rc-service.in \
Expand Down
106 changes: 106 additions & 0 deletions dysnomia-modules/nixos-container.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/bin/bash
set -e
shopt -s nullglob

# Activates or deactivates a NixOS container
#
# Files:
# *-container-settings: Containing global container configuration settings
# *-container-createparams: Linefeed delimited configuration file containing additional command-line arguments passed to: nixos-container create
#
# A settings file is key=value pair based, and supports the following properties:
# containerName: Name of the container (defaults to: $componentName)
# statelessDeployment: If enabled, the container will be removed if it is no
# longer used by any other containers (defaults to: 1)

# Autoconf settings
export prefix=@prefix@

# Import utility functions
source @datadir@/@PACKAGE@/util

# Sets a number of common utility environment variables
composeUtilityVariables $0 $2 $3

source $2/*-container-settings

# Provide default settings
containerName=${containerName:-"$componentName"}
statelessDeployment=${statelessDeployment:-1}

destroyContainer()
{
if [ "$(@nixos_container_bin@ status "$containerName")" = "down" ]
then
@nixos_container_bin@ destroy $containerName
fi
}

case "$1" in
activate)
# Create the container if it does not exist yet
if [ "$(@nixos_container_bin@ status "$containerName" 2> /dev/null)" = "gone" ]
then
(
echo "$containerName"
cat $2/*-container-createparams
echo ""

# If a flake ref has been specified, use that
if [ -n "$flakeRef" ]
then
echo "--flake"
echo "$flakeRef"
else
echo "--config-file"
echo "$2/configuration.nix"
fi
) | @xargs@ -d '\n' @nixos_container_bin@ create
fi

@nixos_container_bin@ start $containerName

if [ "$statelessDeployment" != "1" ]
then
markComponentAsActive
fi
;;
deactivate)
# Stop the container if it is still running
if [ "$(@nixos_container_bin@ status "$containerName")" = "up" ]
then
@nixos_container_bin@ stop $containerName
fi

if [ "$statelessDeployment" = "1" ]
then
destroyContainer
else
markComponentAsGarbage
fi
;;
collect-garbage)
if componentMarkedAsGarbage
then
destroyContainer
unmarkComponentAsGarbage
fi
;;
shell)
cat >&2 <<EOF
This is a shell session that can be used to control the '$componentName' NixOS container.

Module specific environment variables:
containerName Name of the container

Some useful commands:
nixos-container status $containerName # Check status of the container
nixos-container start $containerName # Start the container
nixos-container stop $containerName # Stop the container
nixos-container terminate $containerName # Stop the container forcibly
nixos-container login $containerName # Run a diagnostic shell inside the container as a regular user
nixos-container root-login $containerName # Run a diagnostic shell inside the container as root
nixos-container run $containerName -- <command> # Run <command> inside the container
EOF
;;
esac
4 changes: 4 additions & 0 deletions release.nix
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ let
enableState = true;
};

nixos-container = import ./tests/nixos-container.nix {
inherit nixpkgs tarball buildFun;
};

influx-database = import ./tests/influx-database.nix {
inherit nixpkgs tarball buildFun;
};
Expand Down
32 changes: 32 additions & 0 deletions tests/deployment/nixos-container/configuration.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{config, lib, pkgs, ...}:

with lib;
{
# Minimal container config, as written in the nixos-container script
boot.isContainer = true;
networking.hostName = mkDefault "test";
networking.useDHCP = false;

# Run a simple TCP echo server
services.xinetd = {
enable = true;
# This ugly hack is necessary because the NixOS service does not support
# INTERNAL services
extraDefaults = ''
}

service echo
{
protocol = tcp
type = INTERNAL
socket_type = stream
wait = no
'';
};

# Open the necessary port
networking.firewall.allowedTCPPorts = [ 7 ];

# Silence the warning
system.stateVersion = "22.11";
}
1 change: 1 addition & 0 deletions tests/deployment/nixos-container/test-container-settings
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
containerName=test
108 changes: 108 additions & 0 deletions tests/nixos-container.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{ nixpkgs, tarball, buildFun }:

let
dysnomia = buildFun {
pkgs = import nixpkgs {};
inherit tarball;
enableNixosContainer = true;
};

pkgs = import nixpkgs {};
in
with import "${nixpkgs}/nixos/lib/testing-python.nix" { system = builtins.currentSystem; };
with pkgs;

let
# Pre-evaluate the container derivation, otherwise the VM needs to do it and
# there is no Internet connection in there
sysEval = import "${nixpkgs}/nixos/lib/eval-config.nix";
container = (sysEval {
system = builtins.currentSystem;
modules = [
./deployment/nixos-container/configuration.nix
];
}).config.system.build.toplevel;

component = symlinkJoin {
name = "nixos-container-component";
paths = [
# This defines a simple container implementing the TCP echo protocol
./deployment/nixos-container
# Parameters passed to nixos-container need to be created dynamically
# because we have to provide a path to the pre-built derivation of the
# container
(runCommandLocal "test-container-createparams-cmd" {} ''
mkdir $out

cat >> $out/test-container-createparams << EOF
--host-address
10.235.0.1
--local-address
10.235.0.2
--system-path
${container}
EOF
'').out
];
};

addr = "10.235.0.2";
in
makeTest {
name = "dysnomia-nixos-container-test";
nodes = {
machine = {config, pkgs, ...}:

{
virtualisation.memorySize = 512;
virtualisation.diskSize = 1024;
boot.enableContainers = true;

environment.systemPackages = [ dysnomia ];
};
};

testScript =
''
# This pipeline will write "test" to stdout upon success
testcmd = "echo \"test\" | nc -N ${addr} 7"

def check_container_activated():
machine.succeed("sleep 5")
# Compare output to the expected string
machine.succeed("test " + testcmd + " = \"test\"")


def check_container_deactivated():
machine.succeed("sleep 5")
# Netcat fails with code 1 if the machine is inactive
machine.fail(testcmd)


# Activate the test container and verify that the TCP echo server is working
machine.succeed(
"dysnomia --type nixos-container --operation activate --component ${component} --environment"
)
check_container_activated()

# Activate again. This operation should succeed as it is idempotent.
machine.succeed(
"dysnomia --type nixos-container --operation activate --component ${component} --environment"
)
check_container_activated()

# Deactivate the process. Check if the container is not running anymore, and verify that it has been removed.
machine.succeed(
"dysnomia --type nixos-container --operation deactivate --component ${component} --environment"
)
check_container_deactivated()

machine.succeed("test $(nixos-container list | wc -l) = 0")

# Deactivate again. This operation should succeed as it is idempotent.
machine.succeed(
"dysnomia --type nixos-container --operation deactivate --component ${component} --environment"
)
check_container_deactivated()
'';
}