1
0
mirror of https://github.com/erlang/docker-erlang-example.git synced 2025-07-30 22:43:04 +03:00

Refactoring: Copy minikube example

This commit copies Lukas Larsson's (@garazdawi) and Siri Hansen's
(@sirihansen) example from:

https://github.com/erlang/docker-erlang-example/tree/minikube-dist
This commit is contained in:
Kjell Winblad
2019-05-20 14:13:40 +02:00
parent 61d3fe2a44
commit 81c88928de
25 changed files with 941 additions and 0 deletions

View File

@ -0,0 +1,2 @@
_build/
rebar.lock

View File

@ -0,0 +1,51 @@
sudo: required
dist: xenial
addons:
apt:
packages:
- curl
env:
- CHANGE_MINIKUBE_NONE_USER=true
before_script:
# Download minikube.
- MINIKUBE_VERSION=latest
- curl -Lo minikube https://storage.googleapis.com/minikube/releases/$MINIKUBE_VERSION/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
# Download kubectl, which is a requirement for using minikube.
- KUBERNETES_VERSION=$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)
- curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/$KUBERNETES_VERSION/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
# Test that it works
- kubectl -h
- sudo minikube start -v 7 --logtostderr --vm-driver=none --kubernetes-version "$KUBERNETES_VERSION"
# Fix the kubectl context, as it's often stale.
- minikube update-context
# Wait for Kubernetes to be up and ready.
- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
script:
- kubectl cluster-info
# kube-addon-manager is responsible for managing other kubernetes components, such as kube-dns, dashboard, storage-provisioner..
- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl -n kube-system get pods -lcomponent=kube-addon-manager -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 5;echo "waiting for kube-addon-manager to be available"; kubectl get pods --all-namespaces; done
# Wait for kube-dns to be ready.
- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl -n kube-system get pods -lk8s-app=kube-dns -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 5;echo "waiting for kube-dns to be available"; kubectl get pods --all-namespaces; done
- kubectl create service clusterip backend --tcp=12345:12345
- docker build -t backend -f Dockerfile.backend .
- kubectl apply -f backend-deploy.yaml
- kubectl create service nodeport dockerwatch --tcp=8080:8080 --tcp=8443:8443
- kubectl get service
- ./create-certs $(minikube ip)
- kubectl create secret generic dockerwatch --from-file=ssl/
- kubectl get secret
- docker build -t dockerwatch .
- kubectl apply -f dockerwatch-deploy.yaml
# Wait for dockerwatch to be ready.
- JSONPATH='{range .items[*]}{@.metadata.name}:{@.status.readyReplicas};{end}'; until kubectl -n default get deployment -lapp=dockerwatch -o jsonpath="$JSONPATH" 2>&1 | grep -q ":10"; do sleep 5;echo "waiting for dockerwatch to be available"; kubectl get pods --all-namespaces; done
- HTTP=$(minikube service dockerwatch --url | head -1)
- HTTPS=$(minikube service dockerwatch --url --https | tail -1)
- "until curl -v -H 'Content-Type: application/json' -X POST -d '' $HTTP/cnt; do sleep 10; done"
- "curl -v -H 'Content-Type: application/json' -X POST -d '{}' $HTTP/cnt"
- "curl -v --cacert ssl/dockerwatch-ca.pem -H 'Accept: application/json' $HTTPS/"
- "curl -v --cacert ssl/dockerwatch-ca.pem -H 'Accept: application/json' $HTTPS/cnt"

View File

@ -0,0 +1,26 @@
## Docker Cheatsheet
* Remove all containers that are not running:
$ docker rm $(docker ps -aq -f status=exited)
* Remove dangling images:
$ docker rmi $(docker images -f dangling=true -q)
* Attach to running docker:
$ docker exec -i -t NameOrId /bin/sh
## Core generation
* `/proc/sys/core_pattern` is clearly persisted on the host. Taking note of
its content before starting any endeavour is therefore highly encouraged.
* dockers `--privileged` is necessary for a gdb session to catch the stack,
without privileges, gdb just complains about No stack. Google still is
hardly knowledgeable about this phenomenon...
* setting ulimit on docker run works perfectly, for future googlers (syntax hard to find),
a docker-compose example:
ulimits:
core: -1

View File

@ -0,0 +1,37 @@
# Build stage 0
FROM erlang:alpine
# Install Rebar3
RUN mkdir -p /buildroot/rebar3/bin
ADD https://s3.amazonaws.com/rebar3/rebar3 /buildroot/rebar3/bin/rebar3
RUN chmod a+x /buildroot/rebar3/bin/rebar3
# Setup Environment
ENV PATH=/buildroot/rebar3/bin:$PATH
# Reset working directory
WORKDIR /buildroot
# Copy our Erlang test application
COPY dockerwatch dockerwatch
# And build the release
WORKDIR dockerwatch
RUN rebar3 as prod release
# Build stage 1
FROM alpine
# Install some libs
RUN apk add --no-cache openssl && \
apk add --no-cache ncurses-libs
# Install the released application
COPY --from=0 /buildroot/dockerwatch/_build/prod/rel/dockerwatch /dockerwatch
# Expose relevant ports
EXPOSE 8080
EXPOSE 8443
CMD ["/dockerwatch/bin/dockerwatch", "foreground"]

View File

@ -0,0 +1,37 @@
# Build stage 0
FROM erlang:alpine
# Install Rebar3
RUN mkdir -p /buildroot/rebar3/bin
ADD https://s3.amazonaws.com/rebar3/rebar3 /buildroot/rebar3/bin/rebar3
RUN chmod a+x /buildroot/rebar3/bin/rebar3
# Setup Environment
ENV PATH=/buildroot/rebar3/bin:$PATH
# Reset working directory
WORKDIR /buildroot
# Copy our Erlang test application
COPY backend backend
# And build the release
WORKDIR backend
RUN rebar3 as prod release
# Build stage 1
FROM alpine
# Install some libs
RUN apk add --no-cache openssl && \
apk add --no-cache ncurses-libs
# Install the released application
COPY --from=0 /buildroot/backend/_build/prod/rel/backend /backend
# Expose relevant ports
EXPOSE 8080
EXPOSE 8443
CMD ["/backend/bin/backend", "foreground"]

View File

@ -0,0 +1,23 @@
## Generating Certificate
Generate certificates in subdirectory `ssl`.
### Root CA
$ openssl genrsa -out dockerwatch-ca.key 4096
$ openssl req -x509 -new -nodes -key dockerwatch-ca.key -sha256 -days 1024 -out dockerwatch-ca.pem
### Server Certificate
$ openssl genrsa -out dockerwatch-server.key 4096
Certificate signing request
$ openssl req -new -key dockerwatch-server.key -out dockerwatch-server.csr
The most important field: `Common Name (eg, YOUR name) []: localhost`. We use localhost in this example.
### Sign it
$ openssl x509 -req -in dockerwatch-server.csr -CA dockerwatch-ca.pem -CAkey dockerwatch-ca.key -CAcreateserial -out dockerwatch-server.pem -days 500 -sha256

View File

@ -0,0 +1,264 @@
# Using Minikube and Erlang
This is a quick demo of using minikube to run a distributed Erlang application.
The example we will use is the
[Docker Watch](http://github.com/erlang/docker-erlang-example/tree/master)
node as a base.
This is only meant to be an example of how to get started. It is not the only,
nor neccesarily the best way to setup minikube with distributed Erlang.
# Other Demos
* [Using Docker](http://github.com/erlang/docker-erlang-example/)
* [Using Docker: Logstash](http://github.com/erlang/docker-erlang-example/tree/logstash)
* [Using Docker Compose: Logstash/ElasticSearch/Kibana](http://github.com/erlang/docker-erlang-example/tree/elk)
* [Using Minikube: Simple](http://github.com/erlang/docker-erlang-example/tree/minikube-simple)
* [Using Minikube: Prometheus/Grafana](http://github.com/erlang/docker-erlang-example/tree/minikube-prom-graf)
* [Using Minikube: Distributed Erlang](http://github.com/erlang/docker-erlang-example/tree/minikube-dist)
* [Using Minikube: Encrypted Distributed Erlang](http://github.com/erlang/docker-erlang-example/tree/minikube-tls-dist)
# Prerequisites
To start with you should familiarize yourself with minikube through this guide:
https://kubernetes.io/docs/setup/minikube/
In a nutshell:
## Install
* [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
* [minikube](https://github.com/kubernetes/minikube/releases)
## Start and test
> minikube start
> kubectl run hello-minikube --image=k8s.gcr.io/echoserver:1.10 --port=8080
> kubectl expose deployment hello-minikube --type=NodePort
> curl $(minikube service hello-minikube --url)
## Should print a lot of text
> kubectl delete services hello-minikube
> kubectl delete deployment hello-minikube
> minikube stop
# Deploying Dockerwatch
In this demo we will be doing three things:
* Create a new application called backend for mnesia
* Modify dockerwatch to use mnesia as its storage
* Create a Service and Deployment for the backend
* Create a Deployment of dockerwatch that implements the Service
First however, make sure that the minikube cluster is started:
> minikube start
and that you have cloned this repo and checked out this branch:
> git clone https://github.com/erlang/docker-erlang-example
> cd docker-erlang-example
> git checkout minikube-dist
## Create backend
The purpose of the backend is to be the service responsible for writing
keeping the data. So the only thing it needs to do is some mnesia
initialization when [starting](backend/src/backend_app.erl).
Since we are running inside a kluster where each pod gets its own IP address
we don't really need epmd any more. So in this example we use the `-epmd_module`
to implement our own static epmd client that always returns port 12345 as the
distribution port. The module looks like this:
```
-module(epmd_static).
-export([start_link/0, register_node/2, register_node/3,
port_please/2, address_please/3]).
%% API.
start_link() ->
ignore.
register_node(Name, Port) ->
register_node(Name, Port, inet_tcp).
register_node(_Name, _Port, _Driver) ->
{ok, 0}.
port_please(_Name, _Host) ->
{port, 12345, 5}.
address_please(Name, Host, AddressFamily) ->
erl_epmd:address_please(Name, Host, AddressFamily).
```
The module is then configured to be used in [vm.args.src](backend/config/vm.args.src):
-start_epmd false
-epmd_module epmd_static
Lastly net_kernel has to be configured to listen to the port, this is done in the
[sys.config.src](backend/config/sys.config.src):
```
{kernel, [{logger,[{handler,default,logger_std_h,#{}}]},
%%,{logger_level,info}
{inet_dist_listen_min, 12345},
{inet_dist_listen_max, 12345}
]},
```
## Modify dockerwatch
The modification of the dockerwatch module to use mnesia is pretty straight forward.
You can see the end result [here](dockerwatch/src/dockerwatch.erl).
The same modifications with distribution port has to be done for dockerwatch as well.
As we want to be able to scale the number of dockerwatch frontends as load increases
we need to use a node name that is unique in the cluster. Using the IP address of
the pod is a simple solution that works for now.
In order to get the IP the following config has to be added to the dockerwatch deployment
config:
```
env:
- name: IP
valueFrom:
fieldRef:
fieldPath: status.podIP
```
and then we just use `${IP}` in [vm.args.src](dockerwatch/config/vm.args.src):
-name dockerwatch@${IP}
Also mnesia is configured to connect to the backend service at startup in
[sys.config.src](dockerwatch/config/sys.config.src).
```
{mnesia, [{extra_db_nodes,['dockerwatch@backend.default.svc.cluster.local']}]}
```
## Deploy the backend
We first setup the backend node as a service in order to easily find how to connect with it
from the dockerwatch nodes. This only works if there is only one backend node. If you
want to add more you need to use a more complex solution.
```
kubectl create service clusterip backend --tcp=12345:12345
service/backend created
```
The backend is build like this:
eval $(minikube docker-env)
docker build -t backend -f Dockerfile.backend .
and then deployed like this:
```
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
## Name and labels of the Deployment
labels:
app: backend
name: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
## The container to launch
- image: backend
name: backend
imagePullPolicy: Never
EOF
```
## Deploy Dockerwatch
Dockerwatch is deployed as previously:
```
> kubectl create service nodeport dockerwatch --tcp=8080:8080 --tcp=8443:8443
service/dockerwatch created
> ./create-certs $(minikube ip)
......
> kubectl create secret generic dockerwatch --from-file=ssl/
secret/dockerwatch created
> eval $(minikube docker-env)
> docker build -t dockerwatch .
```
with some small modifications to the config:
```
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
## Name and labels of the Deployment
labels:
app: dockerwatch
name: dockerwatch
spec:
replicas: 10
selector:
matchLabels:
app: dockerwatch
template:
metadata:
labels:
app: dockerwatch
spec:
containers:
## The container to launch
- image: dockerwatch
name: dockerwatch
imagePullPolicy: Never
ports:
- containerPort: 8080
protocol: TCP
- containerPort: 8443
protocol: TCP
volumeMounts:
- name: kube-keypair
readOnly: true
mountPath: /etc/ssl/certs
env:
- name: IP
valueFrom:
fieldRef:
fieldPath: status.podIP
volumes:
- name: kube-keypair
secret:
secretName: dockerwatch
EOF
```
## Testing
We can then test the API using the same curl commands as the [simple demo](http://github.com/erlang/docker-erlang-example/tree/minikube-simple):
```
> curl -H "Content-Type: application/json" -X POST -d "" $(minikube service dockerwatch --url | head -1)/cnt
> curl -H "Content-Type: application/json" -X POST -d "{}" $(minikube service dockerwatch --url | head -1)/cnt
> curl --cacert ssl/dockerwatch-ca.pem -H "Accept: application/json" $(minikube service dockerwatch --url --https | tail -1)
["cnt"]
> curl --cacert ssl/dockerwatch-ca.pem -H "Accept: application/json" $(minikube service dockerwatch --url --https | tail -1)/cnt
{"cnt":2}
```

View File

@ -0,0 +1,33 @@
apiVersion: apps/v1
kind: Deployment
metadata:
## Name and labels of the Deployment
labels:
app: backend
name: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
## The container to launch
- image: backend
name: backend
imagePullPolicy: Never
---
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
ports:
- port: 12345
protocol: TCP
selector:
app: backend

View File

@ -0,0 +1,7 @@
[%% Kernel/logger
{kernel, [{logger,[{handler,default,logger_std_h,#{}}]},
%%,{logger_level,info}
{inet_dist_listen_min, 12345},
{inet_dist_listen_max, 12345}
]}
].

View File

@ -0,0 +1,4 @@
-name dockerwatch@backend.default.svc.cluster.local
-setcookie dockerwatch
-start_epmd false
-epmd_module epmd_static

View File

@ -0,0 +1,16 @@
{deps, []}.
{relx, [{release, {"backend", "1.0.0"}, [backend]},
{vm_args_src, "config/vm.args.src"},
{sys_config_src, "config/sys.config.src"},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}
]}.
{profiles, [{prod, [{relx, [{dev_mode, false},
{include_erts, true},
{include_src, false}]}]}
]}.
%% vim: ft=erlang

View File

@ -0,0 +1,14 @@
%% Feel free to use, reuse and abuse the code in this file.
{application, backend, [
{description, "Mnesia node."},
{vsn, "1.0.0"},
{modules, []},
{registered, [backend_sup]},
{applications, [kernel,
stdlib,
mnesia
]},
{mod, {backend_app, []}},
{env, []}
]}.

View File

@ -0,0 +1,23 @@
%%
%% Copyright (C) 2014 Björn-Egil Dahlberg
%%
%% File: dockerwatch_app.erl
%% Author: Björn-Egil Dahlberg
%% Created: 2014-09-10
%%
-module(backend_app).
-behaviour(application).
-export([start/2,stop/1]).
%% API.
start(_Type, _Args) ->
mnesia:change_table_copy_type(schema, node(), disc_copies),
mnesia:create_table(dockerwatch,
[{disc_copies,[node()]},
{ram_copies,[]}]),
{ok, self()}.
stop(_State) ->
ok.

View File

@ -0,0 +1,27 @@
%%
%% Copyright (C) 2014 Björn-Egil Dahlberg
%%
%% File: dockerwatch_app.erl
%% Author: Björn-Egil Dahlberg
%% Created: 2014-09-10
%%
-module(epmd_static).
-export([start_link/0, register_node/2, register_node/3,
port_please/2, address_please/3]).
%% API.
start_link() ->
ignore.
register_node(Name, Port) ->
register_node(Name, Port, inet_tcp).
register_node(_Name, _Port, _Driver) ->
{ok, 0}.
port_please(_Name, _Host) ->
{port, 12345, 5}.
address_please(Name, Host, AddressFamily) ->
erl_epmd:address_please(Name, Host, AddressFamily).

View File

@ -0,0 +1,22 @@
#!/bin/sh
set -e
if [ ! -d ssl ]; then
mkdir ssl
fi
# Create the root CA (Certificate Authority)
openssl genrsa -out ssl/dockerwatch-ca.key 4096
## Certificate signing request for root CA
openssl req -x509 -new -nodes -key ssl/dockerwatch-ca.key -sha256 -days 1024 -subj "/C=SE/" -out ssl/dockerwatch-ca.pem
# Create the server certificate
openssl genrsa -out ssl/dockerwatch-server.key 4096
## Certificate signing request for server certificate
openssl req -new -key ssl/dockerwatch-server.key -subj "/C=SE/CN=$1/" -out ssl/dockerwatch-server.csr
## Sign the server certificate using the root CA
openssl x509 -req -in ssl/dockerwatch-server.csr -CA ssl/dockerwatch-ca.pem -CAkey ssl/dockerwatch-ca.key -CAcreateserial -out ssl/dockerwatch-server.pem -days 500 -sha256

View File

@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
## Name and labels of the Deployment
labels:
app: dockerwatch
name: dockerwatch
spec:
replicas: 10
selector:
matchLabels:
app: dockerwatch
template:
metadata:
labels:
app: dockerwatch
spec:
containers:
## The container to launch
- image: dockerwatch
name: dockerwatch
imagePullPolicy: Never ## Set to Never as we built the image in the cluster
stdin: true
tty: true
ports:
- containerPort: 8080
protocol: TCP
- containerPort: 8443
protocol: TCP
volumeMounts:
- name: kube-keypair
readOnly: true
mountPath: /etc/ssl/certs
env:
- name: IP
valueFrom:
fieldRef:
fieldPath: status.podIP
volumes:
- name: kube-keypair
secret:
secretName: dockerwatch

View File

@ -0,0 +1,8 @@
[%% Kernel/logger
{kernel, [{logger,[{handler,default,logger_std_h,#{}}]},
%%,{logger_level,info}
{inet_dist_listen_min, 12345},
{inet_dist_listen_max, 12345}
]},
{mnesia, [{extra_db_nodes,['dockerwatch@backend.default.svc.cluster.local']}]}
].

View File

@ -0,0 +1,4 @@
-name dockerwatch@${IP}
-setcookie dockerwatch
-start_epmd false
-epmd_module epmd_static

View File

@ -0,0 +1,17 @@
{deps, [{jsone, "1.4.7"}, %% JSON Encode/Decode
{cowboy, "2.5.0"}]}. %% HTTP Server
{relx, [{release, {"dockerwatch", "1.0.0"}, [dockerwatch]},
{vm_args_src, "config/vm.args.src"},
{sys_config_src, "config/sys.config.src"},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}
]}.
{profiles, [{prod, [{relx, [{dev_mode, false},
{include_erts, true},
{include_src, false}]}]}
]}.
%% vim: ft=erlang

View File

@ -0,0 +1,17 @@
%% Feel free to use, reuse and abuse the code in this file.
{application, dockerwatch, [
{description, "Cowboy REST Hello World example."},
{vsn, "1.0.0"},
{modules, []},
{registered, [dockerwatch_sup]},
{applications, [
kernel,
stdlib,
jsone,
cowboy,
mnesia
]},
{mod, {dockerwatch_app, []}},
{env, []}
]}.

View File

@ -0,0 +1,69 @@
%%
%% Copyright (C) 2014 Björn-Egil Dahlberg
%%
%% File: dockerwatch.erl
%% Author: Björn-Egil Dahlberg
%% Created: 2014-09-10
%%
-module(dockerwatch).
-export([start_link/0, all/0, create/1, get/1, increment/2, decrement/2]).
-type counter() :: binary().
-spec start_link() -> {ok, pid()}.
start_link() ->
ok = mnesia:wait_for_tables([?MODULE], 15000),
ignore.
-spec all() -> [counter()].
all() ->
case mnesia:transaction(
fun() ->
mnesia:select(?MODULE, [{{'$1','_'},[],['$1']}])
end) of
{atomic, Res} ->
Res
end.
-spec create(counter()) -> ok | already_exists.
create(CounterName) ->
case mnesia:transaction(
fun() ->
case mnesia:read(?MODULE, CounterName) of
[] ->
mnesia:write({?MODULE,CounterName,0}),
ok;
_Else ->
already_exists
end
end) of
{atomic, Res} ->
Res
end.
-spec get(counter()) -> integer().
get(CounterName) ->
case mnesia:transaction(
fun() ->
mnesia:read(?MODULE, CounterName)
end) of
{atomic,[{?MODULE, _, Cnt}]} ->
Cnt
end.
-spec increment(counter(), integer()) -> ok.
increment(CounterName, Howmuch) ->
case mnesia:transaction(
fun() ->
[{?MODULE, _, Cnt}] = mnesia:read(?MODULE, CounterName),
mnesia:write({?MODULE, CounterName, Cnt + Howmuch})
end) of
{atomic, ok} ->
ok
end.
-spec decrement(counter(), integer()) -> ok.
decrement(CounterName, Howmuch) ->
increment(CounterName, -1 * Howmuch).

View File

@ -0,0 +1,19 @@
%%
%% Copyright (C) 2014 Björn-Egil Dahlberg
%%
%% File: dockerwatch_app.erl
%% Author: Björn-Egil Dahlberg
%% Created: 2014-09-10
%%
-module(dockerwatch_app).
-behaviour(application).
-export([start/2,stop/1]).
%% API.
start(_Type, _Args) ->
dockerwatch_sup:start_link().
stop(_State) ->
ok.

View File

@ -0,0 +1,103 @@
%%
%% Copyright (C) 2014 Björn-Egil Dahlberg
%%
%% File: dockerwatch_handler.erl
%% Author: Björn-Egil Dahlberg
%% Created: 2014-09-10
%%
-module(dockerwatch_handler).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([handle_post/2]).
-export([to_html/2]).
-export([to_json/2]).
-export([to_text/2]).
init(Req, []) ->
{cowboy_rest, Req, []}.
%% Which HTTP methods are allowed
allowed_methods(Req, State) ->
{[<<"GET">>, <<"POST">>], Req, State}.
%% Which content types are accepted by POST/PUT requests
content_types_accepted(Req, State) ->
{[{{<<"application">>, <<"json">>, []}, handle_post}],
Req, State}.
%% Handle the POST/PUT request
handle_post(Req, State) ->
case cowboy_req:binding(counter_name, Req) of
undefined ->
{false, Req, State};
Name ->
case cowboy_req:has_body(Req) of
true ->
{ok, Body, Req3} = cowboy_req:read_body(Req),
Json = jsone:decode(Body),
ActionBin = maps:get(<<"action">>, Json, <<"increment">>),
Value = maps:get(<<"value">>, Json, 1),
Action = list_to_atom(binary_to_list(ActionBin)),
ok = dockerwatch:Action(Name, Value),
{true, Req3, State};
false ->
ok = dockerwatch:create(Name),
{true, Req, State}
end
end.
%% Which content types we handle for GET/HEAD requests
content_types_provided(Req, State) ->
{[{<<"text/html">>, to_html},
{<<"application/json">>, to_json},
{<<"text/plain">>, to_text}
], Req, State}.
%% Return counters/counter as json
to_json(Req, State) ->
Resp = case cowboy_req:binding(counter_name, Req) of
undefined ->
dockerwatch:all();
Counter ->
#{ Counter => dockerwatch:get(Counter) }
end,
{jsone:encode(Resp), Req, State}.
%% Return counters/counter as plain text
to_text(Req, State) ->
Resp = case cowboy_req:binding(counter_name, Req) of
undefined ->
[io_lib:format("~s~n",[Counter]) || Counter <- dockerwatch:all()];
Counter ->
io_lib:format("~p",[dockerwatch:get(Counter)])
end,
{Resp, Req, State}.
%% Return counters/counter as html
to_html(Req, State) ->
Body = case cowboy_req:binding(counter_name, Req) of
undefined ->
Counters = dockerwatch:all(),
["<ul>\n",
[io_lib:format("<li>~s</li>\n", [Counter]) || Counter <- Counters],
"</ul>\n"];
Counter ->
Value = dockerwatch:get(Counter),
io_lib:format("~s = ~p",[Counter, Value])
end,
{[html_head(),Body,html_tail()], Req, State}.
html_head() ->
<<"<html>
<head>
<meta charset=\"utf-8\">
<title>dockerwatch</title>
</head>">>.
html_tail() ->
<<"</body>
</html>">>.

View File

@ -0,0 +1,49 @@
%%
%% Copyright (C) 2014 Björn-Egil Dahlberg
%%
%% File: dockerwatch_sup.erl
%% Author: Björn-Egil Dahlberg
%% Created: 2014-09-10
%%
-module(dockerwatch_sup).
-behaviour(supervisor).
-export([start_link/0,init/1]).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%% supervisor.
init([]) ->
CertsDir = "/etc/ssl/certs/",
Dispatch = cowboy_router:compile([
{'_', [{"/[:counter_name]", dockerwatch_handler, []}]}
]),
HTTPS = ranch:child_spec(
cowboy_https, 100, ranch_ssl,
[{port, 8443},
{cacertfile, filename:join(CertsDir, "dockerwatch-ca.pem")},
{certfile, filename:join(CertsDir, "dockerwatch-server.pem")},
{keyfile, filename:join(CertsDir, "dockerwatch-server.key")}],
cowboy_tls,
#{env=>#{dispatch=>Dispatch}}),
HTTP = ranch:child_spec(
cowboy_http, 100, ranch_tcp,
[{port, 8080}],
cowboy_clear,
#{env=>#{dispatch=>Dispatch}}),
Counter = {dockerwatch, {dockerwatch, start_link, []},
permanent, 5000, worker, [dockerwatch]},
Procs = [Counter, HTTP, HTTPS],
{ok, {{one_for_one, 10, 10}, Procs}}.

View File

@ -0,0 +1,27 @@
%%
%% Copyright (C) 2014 Björn-Egil Dahlberg
%%
%% File: dockerwatch_app.erl
%% Author: Björn-Egil Dahlberg
%% Created: 2014-09-10
%%
-module(epmd_static).
-export([start_link/0, register_node/2, register_node/3,
port_please/2, address_please/3]).
%% API.
start_link() ->
ignore.
register_node(Name, Port) ->
register_node(Name, Port, inet_tcp).
register_node(_Name, _Port, _Driver) ->
{ok, 0}.
port_please(_Name, _Host) ->
{port, 12345, 5}.
address_please(Name, Host, AddressFamily) ->
erl_epmd:address_please(Name, Host, AddressFamily).