Then go ahead!
TIL that Nix can natively fetch Consul HTTP API with builtins.fetchurl
. This combined with Nix templating/abstract abilities is awesome!
So I had to live with this consul-template
template file:
{{- range services -}}
{{- $service:=.Name -}}
{{- range .Tags -}}
{{- if eq . "cluster" -}}
upstream {{$service}} {
zone upstream-{{$service}} 64k;
{{ range service $service "passing" }}
server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
{{- else -}}server 127.0.0.1:65535; # force a 502
{{- end}}
}
{{- end -}}
{{- end -}}
{{- end -}}
{{range services}}
{{$service:=.Name}} {{range .Tags}} {{if eq . "cluster" }}
server {
server_name {{$service}}.services.example.com;
location / {
proxy_pass http://{{$service}};
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
}
} {{end}} {{end}} {{end}}
It looks so crappy because this templating language is so crappy. You have to fight with whitespace because otherwise your generated config will be unreadable.
But what it looks like in language you know and love?
let
services = builtins.fromJSON (builtins.readFile (builtins.fetchurl "http://localhost:8500/v1/agent/services"));
lib = import <nixpkgs/lib>;
inherit (lib) flip;
# From https://github.com/NixOS/nixpkgs/pull/57091/files
combined = x:
if builtins.isFunction x
then arg1: arg2: builtins.concatStringsSep "" (x arg2 arg1)
else if builtins.isList x
then builtins.concatStringsSep "" x
else if builtins.isString x
# then this is a separator
then y:
if builtins.isFunction y
then arg1: arg2: builtins.concatStringsSep x (y arg2 arg1)
# otherwise this must be a list
else builtins.concatStringsSep x y
else throw "'combined' called with unexpected arguments";
interestingServices = lib.filter (service: lib.elem "cluster" service.Tags) (builtins.attrValues services);
zipped = lib.zipAttrs (map (service: { ${service.Service} = service; }) interestingServices);
in
combined "\n" lib.mapAttrsToList zipped (name: services: ''
upstream ${name} {
zone upstream-${name} 64k;
${ combined "\n" map services (service:
"server ${service.Address}:${toString service.Port} max_fails=3 fail_timeout=60 weight=1;"
)}
}
server {
server_name ${name}.services.example.com;
location / {
proxy_pass http://${name};
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
}
}
'')
You can render template with nix eval -f ./template.nix '' --raw
and subscribe to incoming events with consul watch -type=services
.
So clean and functional!
-
https://github.com/NixOS/nixpkgs/pull/57091, real world usage of
combined map
idiom - https://github.com/NixOS/nix/pull/2490, to make Nix really good at templating
- maybe add
builtins.unsafeCurl
which omits store entirely and allows pass HTTP headers?