diff options
-rw-r--r-- | nixos/modules/services/web-servers/traefik.nix | 125 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 2 | ||||
-rw-r--r-- | nixos/tests/traefik.nix | 87 | ||||
-rw-r--r-- | pkgs/servers/traefik/default.nix | 42 |
4 files changed, 194 insertions, 62 deletions
diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix index 5b0fc467ea46..4ab7307c3b67 100644 --- a/nixos/modules/services/web-servers/traefik.nix +++ b/nixos/modules/services/web-servers/traefik.nix @@ -4,56 +4,102 @@ with lib; let cfg = config.services.traefik; - configFile = - if cfg.configFile == null then - pkgs.runCommand "config.toml" { - buildInputs = [ pkgs.remarshal ]; - preferLocalBuild = true; - } '' - remarshal -if json -of toml \ - < ${pkgs.writeText "config.json" (builtins.toJSON cfg.configOptions)} \ - > $out - '' - else cfg.configFile; - + jsonValue = with types; + let + valueType = nullOr (oneOf [ + bool + int + float + str + (lazyAttrsOf valueType) + (listOf valueType) + ]) // { + description = "JSON value"; + emptyValue.value = { }; + }; + in valueType; + dynamicConfigFile = if cfg.dynamicConfigFile == null then + pkgs.runCommand "config.toml" { + buildInputs = [ pkgs.remarshal ]; + preferLocalBuild = true; + } '' + remarshal -if json -of toml \ + < ${ + pkgs.writeText "dynamic_config.json" + (builtins.toJSON cfg.dynamicConfigOptions) + } \ + > $out + '' + else + cfg.dynamicConfigFile; + staticConfigFile = if cfg.staticConfigFile == null then + pkgs.runCommand "config.toml" { + buildInputs = [ pkgs.yj ]; + preferLocalBuild = true; + } '' + yj -jt -i \ + < ${ + pkgs.writeText "static_config.json" (builtins.toJSON + (recursiveUpdate cfg.staticConfigOptions { + providers.file.filename = "${dynamicConfigFile}"; + })) + } \ + > $out + '' + else + cfg.staticConfigFile; in { options.services.traefik = { enable = mkEnableOption "Traefik web server"; - configFile = mkOption { + staticConfigFile = mkOption { default = null; - example = literalExample "/path/to/config.toml"; + example = literalExample "/path/to/static_config.toml"; type = types.nullOr types.path; description = '' - Path to verbatim traefik.toml to use. - (Using that option has precedence over <literal>configOptions</literal>) + Path to traefik's static configuration to use. + (Using that option has precedence over <literal>staticConfigOptions</literal> and <literal>dynamicConfigOptions</literal>) ''; }; - configOptions = mkOption { + staticConfigOptions = mkOption { description = '' - Config for Traefik. + Static configuration for Traefik. ''; - type = types.attrs; - default = { - defaultEntryPoints = ["http"]; - entryPoints.http.address = ":80"; - }; + type = jsonValue; + default = { entryPoints.http.address = ":80"; }; example = { - defaultEntrypoints = [ "http" ]; - web.address = ":8080"; + entryPoints.web.address = ":8080"; entryPoints.http.address = ":80"; - file = {}; - frontends = { - frontend1 = { - backend = "backend1"; - routes.test_1.rule = "Host:localhost"; - }; - }; - backends.backend1 = { - servers.server1.url = "http://localhost:8000"; + api = { }; + }; + }; + + dynamicConfigFile = mkOption { + default = null; + example = literalExample "/path/to/dynamic_config.toml"; + type = types.nullOr types.path; + description = '' + Path to traefik's dynamic configuration to use. + (Using that option has precedence over <literal>dynamicConfigOptions</literal>) + ''; + }; + + dynamicConfigOptions = mkOption { + description = '' + Dynamic configuration for Traefik. + ''; + type = jsonValue; + default = { }; + example = { + http.routers.router1 = { + rule = "Host(`localhost`)"; + service = "service1"; }; + + http.services.service1.loadBalancer.servers = + [{ url = "http://localhost:8080"; }]; }; }; @@ -61,7 +107,7 @@ in { default = "/var/lib/traefik"; type = types.path; description = '' - Location for any persistent data traefik creates, ie. acme + Location for any persistent data traefik creates, ie. acme ''; }; @@ -84,16 +130,15 @@ in { }; config = mkIf cfg.enable { - systemd.tmpfiles.rules = [ - "d '${cfg.dataDir}' 0700 traefik traefik - -" - ]; + systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0700 traefik traefik - -" ]; systemd.services.traefik = { description = "Traefik web server"; after = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = ''${cfg.package.bin}/bin/traefik --configfile=${configFile}''; + ExecStart = + "${cfg.package}/bin/traefik --configfile=${staticConfigFile}"; Type = "simple"; User = "traefik"; Group = cfg.group; @@ -120,6 +165,6 @@ in { isSystemUser = true; }; - users.groups.traefik = {}; + users.groups.traefik = { }; }; } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 5819879b30e9..4f60892488ab 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -303,6 +303,8 @@ in timezone = handleTest ./timezone.nix {}; tinydns = handleTest ./tinydns.nix {}; tor = handleTest ./tor.nix {}; + # traefik test relies on docker-containers + traefik = handleTestOn ["x86_64-linux"] ./traefik.nix {}; transmission = handleTest ./transmission.nix {}; trac = handleTest ./trac.nix {}; trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {}; diff --git a/nixos/tests/traefik.nix b/nixos/tests/traefik.nix new file mode 100644 index 000000000000..0e21a7cf8437 --- /dev/null +++ b/nixos/tests/traefik.nix @@ -0,0 +1,87 @@ +# Test Traefik as a reverse proxy of a local web service +# and a Docker container. +import ./make-test-python.nix ({ pkgs, ... }: { + name = "traefik"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ joko ]; + }; + + nodes = { + client = { config, pkgs, ... }: { + environment.systemPackages = [ pkgs.curl ]; + }; + traefik = { config, pkgs, ... }: { + docker-containers.nginx = { + extraDockerOptions = [ + "-l" "traefik.enable=true" + "-l" "traefik.http.routers.nginx.entrypoints=web" + "-l" "traefik.http.routers.nginx.rule=Host(`nginx.traefik.test`)" + ]; + image = "nginx-container"; + imageFile = pkgs.dockerTools.examples.nginx; + }; + + networking.firewall.allowedTCPPorts = [ 80 ]; + + services.traefik = { + enable = true; + + dynamicConfigOptions = { + http.routers.simplehttp = { + rule = "Host(`simplehttp.traefik.test`)"; + entryPoints = [ "web" ]; + service = "simplehttp"; + }; + + http.services.simplehttp = { + loadBalancer.servers = [{ + url = "http://127.0.0.1:8000"; + }]; + }; + }; + + staticConfigOptions = { + global = { + checkNewVersion = false; + sendAnonymousUsage = false; + }; + + entryPoints.web.address = ":80"; + + providers.docker.exposedByDefault = false; + }; + }; + + systemd.services.simplehttp = { + script = "${pkgs.python3}/bin/python -m http.server 8000"; + serviceConfig.Type = "simple"; + wantedBy = [ "multi-user.target" ]; + }; + + users.users.traefik.extraGroups = [ "docker" ]; + }; + }; + + testScript = '' + start_all() + + traefik.wait_for_unit("docker-nginx.service") + traefik.wait_until_succeeds("docker ps | grep nginx-container") + traefik.wait_for_unit("simplehttp.service") + traefik.wait_for_unit("traefik.service") + traefik.wait_for_open_port(80) + traefik.wait_for_unit("multi-user.target") + + client.wait_for_unit("multi-user.target") + + with subtest("Check that a container can be reached via Traefik"): + assert "Hello from NGINX" in client.succeed( + "curl -sSf -H Host:nginx.traefik.test http://traefik/" + ) + + with subtest("Check that dynamic configuration works"): + assert "Directory listing for " in client.succeed( + "curl -sSf -H Host:simplehttp.traefik.test http://traefik/" + ) + ''; +}) diff --git a/pkgs/servers/traefik/default.nix b/pkgs/servers/traefik/default.nix index d2a784cdf131..0487ed177d04 100644 --- a/pkgs/servers/traefik/default.nix +++ b/pkgs/servers/traefik/default.nix @@ -1,39 +1,37 @@ -{ stdenv, buildGoPackage, fetchFromGitHub, bash, go-bindata}: +{ stdenv, buildGoModule, fetchFromGitHub, go-bindata, nixosTests }: -buildGoPackage rec { +buildGoModule rec { pname = "traefik"; - version = "1.7.14"; - - goPackagePath = "github.com/containous/traefik"; + version = "2.2.0"; src = fetchFromGitHub { owner = "containous"; repo = "traefik"; rev = "v${version}"; - sha256 = "1j3p09j8rpdkp8v4d4mz224ddakkvhzchvccm9qryrqc2fq4022v"; + sha256 = "1dcazssabqxr9wv3dds3z7ks3y628qa07vgnn3hpdwxzm2b2ma92"; }; - nativeBuildInputs = [ go-bindata bash ]; - - buildPhase = '' - runHook preBuild - ( - cd go/src/github.com/containous/traefik - bash ./script/make.sh generate - - CODENAME=$(awk -F "=" '/CODENAME=/ { print $2}' script/binary) - go build -ldflags "\ - -X github.com/containous/traefik/version.Version=${version} \ - -X github.com/containous/traefik/version.Codename=$CODENAME \ - " -a -o $bin/bin/traefik ./cmd/traefik - ) - runHook postBuild + modSha256 = "0w3ssxvsmq8i6hbfmn4ig2x13i2nlqy5q1khcblf9pq5vhk202qx"; + subPackages = [ "cmd/traefik" ]; + + nativeBuildInputs = [ go-bindata ]; + + passthru.tests = { inherit (nixosTests) traefik; }; + + preBuild = '' + go generate + + CODENAME=$(awk -F "=" '/CODENAME=/ { print $2}' script/binary) + + makeFlagsArray+=("-ldflags=\ + -X github.com/containous/traefik/version.Version=${version} \ + -X github.com/containous/traefik/version.Codename=$CODENAME") ''; meta = with stdenv.lib; { homepage = "https://traefik.io"; description = "A modern reverse proxy"; license = licenses.mit; - maintainers = with maintainers; [ hamhut1066 vdemeester ]; + maintainers = with maintainers; [ vdemeester ]; }; } |