From 48c4ecafae8d824a8cff60a405ebd94496bf718f Mon Sep 17 00:00:00 2001 From: Jeltz Date: Thu, 2 May 2024 22:28:50 +0200 Subject: [PATCH] WIP: caddy: add support for error msg --- host_vars/proxy.pub.infra.auro.re.yml | 14 ++-- roles/caddy/defaults/main.yml | 3 + roles/caddy/filter_plugins/caddy.py | 110 ++++++++++++++++---------- roles/caddy/tasks/main.yml | 18 ++++- roles/caddy/templates/caddy.json.j2 | 1 - roles/caddy/templates/error.html.j2 | 107 +++++++++++++++++++++++++ 6 files changed, 203 insertions(+), 50 deletions(-) create mode 100644 roles/caddy/defaults/main.yml delete mode 100644 roles/caddy/templates/caddy.json.j2 create mode 100644 roles/caddy/templates/error.html.j2 diff --git a/host_vars/proxy.pub.infra.auro.re.yml b/host_vars/proxy.pub.infra.auro.re.yml index 149c61d..d0ef62b 100644 --- a/host_vars/proxy.pub.infra.auro.re.yml +++ b/host_vars/proxy.pub.infra.auro.re.yml @@ -31,17 +31,21 @@ caddy__routes_https: status: 301 www3.test.auro.re: reverse: - - "[2a09:6840:128::98]:3000" - - 10.128.0.98:3000 + - "[2a09:6840:128::198]:3000" + - 10.128.0.198:3000 + +caddy__errors: + - root: /var/www + - rewrite: /error.html + - file_server: true + templates: true caddy__servers: https: listen: ":443" routes: "{{ caddy__routes_https }}" + errors: "{{ caddy__errors }}" http: listen: ":80" -#caddy__error_handlers: -#"*": -# - error_template: "/var/www/error.html" ... diff --git a/roles/caddy/defaults/main.yml b/roles/caddy/defaults/main.yml new file mode 100644 index 0000000..69e5d7f --- /dev/null +++ b/roles/caddy/defaults/main.yml @@ -0,0 +1,3 @@ +--- +caddy__error_dir: /var/www/error +... diff --git a/roles/caddy/filter_plugins/caddy.py b/roles/caddy/filter_plugins/caddy.py index 732bb9c..05c74e0 100644 --- a/roles/caddy/filter_plugins/caddy.py +++ b/roles/caddy/filter_plugins/caddy.py @@ -14,8 +14,8 @@ from pydantic import ( T = TypeVar("T") -def flatten(iterable: Iterable[Iterable[T]]) -> Iterable[T]: - return itertools.chain.from_iterable(iterable) +def flatten_list(iterable: Iterable[Iterable[T]]) -> list[T]: + return list(itertools.chain.from_iterable(iterable)) class AutoList(list[T], Generic[T]): @@ -43,7 +43,8 @@ class FilesHandler(BaseHandler): root: str def to_caddy(self): - yield {"handle": [{"handler": "vars", "root": self.root}]} + handler = {"handler": "vars", "root": self.root} + yield {"handle": [handler]} class StaticHandler(BaseHandler): @@ -51,35 +52,60 @@ class StaticHandler(BaseHandler): body: str | None = None def to_caddy(self): - response = {"handler": "static_response"} + handler = {"handler": "static_response"} if self.status is not None: - response["status_code"] = self.status + handler["status_code"] = self.status if self.body is not None: - response["body"] = self.body - yield {"handle": [response]} + handler["body"] = self.body + yield {"handle": [handler]} class ReverseHandler(BaseHandler): reverse: AutoList[str] def to_caddy(self): - yield { - "handle": [ - { - "handler": "reverse_proxy", - "upstreams": [{"dial": s} for s in self.reverse], - } - ] + handler = { + "handler": "reverse_proxy", + "upstreams": [{"dial": s} for s in self.reverse], } + yield {"handle": [handler]} -Handler = FilesHandler | ReverseHandler | StaticHandler +class RewriteHandler(BaseHandler): + rewrite: str + + def to_caddy(self): + handler = {"handler": "rewrite", "uri": self.rewrite} + yield {"handle": [handler]} + + +class FileServerHandler(BaseHandler): + file_server: Literal[True] + templates: bool = False + + def to_caddy(self): + handlers = [ + {"handler": "templates"}, + ] + if self.templates: + handlers.append({"handler": "file_server"}) + yield {"handle": handlers} + + +Handler = ( + FilesHandler + | ReverseHandler + | RewriteHandler + | FileServerHandler + | StaticHandler +) Routes = dict[str, AutoList[Handler]] class Server(BaseModel): listen: AutoList[str] routes: Routes = {} + errors: AutoList[Handler] = {} Config = dict[str, Server] @@ -96,23 +122,22 @@ class Context: def strip_path_prefix(prefix: str) -> Any: return { - "strip_path_prefix": prefix, "handler": "rewrite", + "strip_path_prefix": prefix, } def handler_to_caddy(handler: Handler, ctx: Context) -> Any: def to_caddy_inner(): if handler.headers: - yield { - "handle": [ - { - "handler": "headers", - "response": {"set": {name: [value]}}, - } - for name, value in handler.headers.items() - ] - } + handlers = [ + { + "handler": "headers", + "response": {"set": {name: [value]}}, + } + for name, value in handler.headers.items() + ] + yield {"handle": handlers} yield from handler.to_caddy() if handler.path is None: @@ -133,30 +158,29 @@ def handler_to_caddy(handler: Handler, ctx: Context) -> Any: } -def route_to_caddy(host: str, handlers: list[Handler], ctx: Context) -> Any: - return { - "match": [{"host": [host]}], - "handle": [ - { - "handler": "subroute", - "routes": list( - flatten(handler_to_caddy(h, ctx) for h in handlers) - ), - } - ], - "terminal": True, +def route_to_caddy( + host: str | None, handlers: list[Handler], ctx: Context +) -> Any: + handler = { + "handler": "subroute", + "routes": flatten_list(handler_to_caddy(h, ctx) for h in handlers), } + route = {"handle": [handler], "terminal": True} + if host is not None: + route["match"] = [{"host": [host]}] + return route def server_to_caddy(server: Server, ctx: Context) -> Any: + routes = [ + route_to_caddy(host, handlers, ctx) + for host, handlers in server.routes.items() + ] return { "listen": server.listen, - "errors": {}, # TODO - "logs": {}, # TODO - "routes": [ - route_to_caddy(host, handlers, ctx) - for host, handlers in server.routes.items() - ], + "errors": {"routes": [route_to_caddy(None, server.errors, ctx)]}, + "logs": {}, + "routes": routes, } diff --git a/roles/caddy/tasks/main.yml b/roles/caddy/tasks/main.yml index 4c7b837..cd35f57 100644 --- a/roles/caddy/tasks/main.yml +++ b/roles/caddy/tasks/main.yml @@ -23,7 +23,7 @@ - name: Configure caddy copy: - content: "{{ caddy__servers | caddy__of_servers | to_json }}" + content: "{{ caddy__servers | caddy__of_servers | to_nice_json }}" dest: /etc/caddy/caddy.json owner: root group: root @@ -31,6 +31,22 @@ notify: - Reload caddy +- name: Create error directory + file: + path: "{{ caddy__error_dir }}" + state: directory + owner: root + group: root + mode: u=rwx=,g=rx,o=rx + +- name: Configure error page + template: + src: error.html.j2 + dest: "{{ caddy__error_dir }}/error.html" + owner: root + group: root + mode: u=rw,g=r,o=r + - name: Enable caddy systemd: daemon_reload: true diff --git a/roles/caddy/templates/caddy.json.j2 b/roles/caddy/templates/caddy.json.j2 deleted file mode 100644 index b0af213..0000000 --- a/roles/caddy/templates/caddy.json.j2 +++ /dev/null @@ -1 +0,0 @@ -{{ caddy__servers | caddy__of_servers | to_nice_json }} diff --git a/roles/caddy/templates/error.html.j2 b/roles/caddy/templates/error.html.j2 new file mode 100644 index 0000000..0c86f9c --- /dev/null +++ b/roles/caddy/templates/error.html.j2 @@ -0,0 +1,107 @@ + +{{ ansible_managed | comment("xml") }} + + + + {{ '{{placeholder "http.error.status_code"}}' }} + + + + + + +

Erreur {{ '{{placeholder "http.error.status_code"}}' }}

+

Error {{ '{{placeholder "http.error.status_code"}}' }}

+ {{ '{{$http_code := atoi (placeholder "http.error.status_code")}}' }} + {{ '{{if eq $http_code 502 503}}' }} + +

Whoops, the service is temporarily unavailable or under maintenance…
Try refreshing the page later.

+ {{ '{{else if eq $http_code 504}}' }} + +

Whoops, the service takes too long to respond…
Try refreshing the page later.

+ {{ '{{end}}' }} + {{ '{{if and (lt $http_code 600) (gt $http_code 499)}}' }} + +

If the problem persists, contact the Aurore technical team.

+ {{ '{{end}}' }} + {{ '{{if eq $http_code 403}}' }} + +

Whoops, you are not allowed on this page.

+ {{ '{{else if eq $http_code 404}}' }} + +

Whoops, this page doesn't exist.

+ {{ '{{else if and (lt $http_code 499) (gt $http_code 399)}}' }} + +

Whoops, malformed request.

+ {{ '{{end}}' }} + +

Support ID: {{ '{{placeholder "http.error.id"}}' }}

+ +