Current section

Files

Jump to
erldns src admin erldns_admin_zone_handler.erl
Raw

src/admin/erldns_admin_zone_handler.erl

-module(erldns_admin_zone_handler).
-moduledoc """
Support for the erldns Admin API root resource at path `/zones/:name`.
The following is implemented:
- `DELETE`: Deletes a zone from cache.
- `GET`: Returns information about records in a given zone cached in erldns.
Acceps an optional query parameter `metaonly`, that takes a boolean: if set to `true`
only zone metadata is returned, without the actual `"records"`.
The response JSON body looks like the following:
```json
{
"erldns": {
"zone": {
"name": "example.com",
"version": "v1.2.3",
"records_count": 11,
"records": [
{
"name": "example.com.",
"type": "A",
"ttl": 3600,
"content": "1.2.3.4"
},
{
"name": "example.com.",
"type": "AAAA",
"ttl": 3600,
"content": "2001:6A8:0:1:210:4BFF:FE4B:4C61"
},
...
]
}
}
}
```
""".
-behaviour(cowboy_rest).
-include_lib("kernel/include/logger.hrl").
-define(LOG_METADATA, #{domain => [erldns, admin]}).
-export([
init/2,
content_types_provided/2,
resource_exists/2,
allowed_methods/2,
delete_resource/2
]).
-export([to_html/2, to_text/2, to_json/2]).
-doc false.
-spec init(cowboy_req:req(), dynamic()) ->
{cowboy_rest, cowboy_req:req(), dynamic()}.
init(Req, State) ->
{cowboy_rest, Req, State}.
-doc false.
-spec allowed_methods(cowboy_req:req(), dynamic()) ->
{[binary()], cowboy_req:req(), dynamic()}.
allowed_methods(Req, State) ->
{[~"GET", ~"DELETE"], Req, State}.
-doc false.
-spec content_types_provided(cowboy_req:req(), dynamic()) ->
{[{{binary(), binary(), '*'}, atom()}], cowboy_req:req(), dynamic()}.
content_types_provided(Req, State) ->
ContentTypesProvided = [
{{~"application", ~"json", '*'}, to_json},
{{~"text", ~"html", '*'}, to_html},
{{~"text", ~"plain", '*'}, to_text}
],
{ContentTypesProvided, Req, State}.
-doc false.
-spec resource_exists(cowboy_req:req(), dynamic()) ->
{boolean(), cowboy_req:req(), dynamic()}.
resource_exists(Req, State) ->
Name = cowboy_req:binding(zonename, Req),
{erldns_zone_cache:is_in_any_zone(Name), Req, State}.
-doc false.
-spec delete_resource(cowboy_req:req(), dynamic()) ->
{boolean(), cowboy_req:req(), dynamic()}.
delete_resource(Req, State) ->
Name = cowboy_req:binding(zonename, Req),
?LOG_NOTICE(#{what => delete_zone_request, resource => Name}, ?LOG_METADATA),
erldns_zone_cache:delete_zone(Name),
{true, Req, State}.
-doc false.
-spec to_html(cowboy_req:req(), dynamic()) ->
{cowboy_req:resp_body(), cowboy_req:req(), dynamic()}.
to_html(Req, State) ->
{~"erldns admin", Req, State}.
-doc false.
-spec to_text(cowboy_req:req(), dynamic()) ->
{cowboy_req:resp_body(), cowboy_req:req(), dynamic()}.
to_text(Req, State) ->
{~"erldns admin", Req, State}.
-doc false.
-spec to_json(cowboy_req:req(), dynamic()) ->
{stop | cowboy_req:resp_body(), cowboy_req:req(), dynamic()}.
to_json(Req, State) ->
ZoneName = cowboy_req:binding(zonename, Req),
Params = cowboy_req:parse_qs(Req),
?LOG_DEBUG(
#{what => received_get, resource => ZoneName, params => Params},
?LOG_METADATA
),
case erldns_zone_cache:lookup_zone(ZoneName) of
zone_not_found ->
?LOG_ERROR(#{what => get_zone_error, error => zone_not_found}, ?LOG_METADATA),
Resp = "Error getting zone: zone not found",
{stop, cowboy_req:reply(400, #{}, Resp, Req), State};
Zone ->
MaybeMetaOnly = lists:keymember(~"metaonly", 1, Params),
Mode = choose_mode(MaybeMetaOnly),
Json = erldns_zone_codec:encode(Zone, #{mode => Mode}),
Body = json:encode(Json),
{Body, Req, State}
end.
-spec choose_mode(boolean()) -> atom().
choose_mode(true) ->
zone_meta_to_json;
choose_mode(false) ->
zone_to_json.