Skip to content
This repository was archived by the owner on Dec 24, 2025. It is now read-only.

Commit 5cb3ddc

Browse files
committed
Allow expressing the port to use when proxying the application
In order to properly load balance instances of a given application with Traefik is better to configure the port used to expose the service. Generally Traefik can deduct that based on different critieria but fixing it is probably the most deterministic behaviour and it also doesn't require the container to actively expose anything on the host directly.
1 parent c891aef commit 5cb3ddc

9 files changed

Lines changed: 107 additions & 41 deletions

File tree

.formatter.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ locals_without_parens = [
1010
env: 2,
1111
expose_port: 2,
1212
proxy: 1,
13-
publish_on_domain: 1
13+
publish_on_domain: 2
1414
]
1515

1616
[

lib/makina/docker.ex

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -170,21 +170,11 @@ defmodule Makina.Docker do
170170
"traefik.http.middlewares.#{app_name(app)}.compress=true",
171171
"traefik.http.routers.#{app_name(app)}.rule=\"#{format_domains(domains)}\"",
172172
"traefik.http.routers.#{app_name(app)}.tls.certresolver=letsencrypt",
173-
"traefik.http.services.#{app_name(app)}.loadBalancer.server.port=#{first_exposed_port(app)}"
173+
"traefik.http.services.#{app_name(app)}.loadBalancer.server.port=#{app.load_balancing_port}"
174174
]
175175
end
176176

177177
defp format_domains(domains) when is_list(domains) do
178178
domains |> Enum.map_join(" || ", fn d -> "Host(\\`#{d}\\`)" end)
179179
end
180-
181-
defp first_exposed_port(%Application{exposed_ports: []}) do
182-
"8080"
183-
end
184-
185-
defp first_exposed_port(%Application{} = app) do
186-
port_pair = app.exposed_ports |> List.first()
187-
188-
port_pair.external
189-
end
190180
end

lib/makina/dsl/app.ex

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ defmodule Makina.DSL.App do
131131
end
132132
end
133133

134+
@publish_on_domain_opts [from_port: [type: :pos_integer, required: true]]
134135
@doc """
135136
Exposes the app on the given domain
136137
Can be invoked multiple times or with a list;
@@ -152,27 +153,65 @@ defmodule Makina.DSL.App do
152153
makina "example" do
153154
app name: "foo" do
154155
155-
publish_on_domain ["example.com", "www.example.com"]
156+
publish_on_domain ["example.com", "www.example.com"], from_port: 80
156157
157158
end
158159
end
159160
"""
160-
defmacro publish_on_domain(domain) when is_binary(domain) do
161-
quote bind_quoted: [domain: domain] do
162-
@current_application Application.put_domain(
163-
@current_application,
164-
domain
165-
)
161+
defmacro publish_on_domain(domain, opts) when is_binary(domain) do
162+
schema = @publish_on_domain_opts
163+
164+
quote bind_quoted: [domain: domain, schema: schema, opts: opts] do
165+
validation = NimbleOptions.validate(opts, schema)
166+
167+
case validation do
168+
{:ok, opts} ->
169+
@current_application Application.put_domain(
170+
@current_application,
171+
domain
172+
)
173+
174+
@current_application Application.set_load_balancing_port(
175+
@current_application,
176+
opts[:from_port]
177+
)
178+
179+
{:error, error} ->
180+
raise """
181+
The parameters provided to the `publish_on_domain` statement are not correct:
182+
183+
#{Exception.message(error)}
184+
"""
185+
end
166186
end
167187
end
168188

169-
defmacro publish_on_domain(domains) when is_list(domains) do
170-
quote bind_quoted: [domains: domains] do
171-
for d <- domains do
172-
@current_application Application.put_domain(
173-
@current_application,
174-
d
175-
)
189+
defmacro publish_on_domain(domains, opts) when is_list(domains) do
190+
schema = @publish_on_domain_opts
191+
192+
quote bind_quoted: [domains: domains, opts: opts, schema: schema] do
193+
validation = NimbleOptions.validate(opts, schema)
194+
195+
case validation do
196+
{:ok, opts} ->
197+
for d <- domains do
198+
@current_application Application.put_domain(
199+
@current_application,
200+
d
201+
)
202+
end
203+
204+
@current_application Application.set_load_balancing_port(
205+
@current_application,
206+
opts[:from_port]
207+
)
208+
209+
{:error, error} ->
210+
raise """
211+
The parameters provided to the `publish_on_domain` statement are not correct:
212+
213+
#{Exception.message(error)}
214+
"""
176215
end
177216
end
178217
end

lib/makina/dsl/utils.ex

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
defmodule Makina.DSL.Utils do
22
@moduledoc false
33

4-
def unset_wrapping_context() do
5-
quote do
6-
Module.delete_attribute(__MODULE__, :wrapping_context)
7-
end
8-
end
9-
104
def set_scope(scope) when is_list(scope) do
115
for s <- scope do
126
set_scope(s)

lib/makina/models/application.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ defmodule Makina.Models.Application do
3232

3333
alias Makina.Models.Internal
3434

35-
@hashable_keys ~w[name docker_image docker_registry env_vars volumes exposed_ports domains]a
35+
@hashable_keys ~w[name docker_image docker_registry env_vars volumes exposed_ports domains load_balancing_port]a
3636

3737
@derive {JSON.Encoder, []}
3838
defstruct __hash__: nil,
@@ -48,7 +48,8 @@ defmodule Makina.Models.Application do
4848
volumes: [],
4949
env_vars: [],
5050
exposed_ports: [],
51-
domains: []
51+
domains: [],
52+
load_balancing_port: nil
5253

5354
def new(opts) do
5455
app = struct(__MODULE__, opts)
@@ -99,6 +100,12 @@ defmodule Makina.Models.Application do
99100
set_private(app, :__hash__, hash(app))
100101
end
101102

103+
def set_load_balancing_port(%__MODULE__{} = app, port) do
104+
app = %__MODULE__{app | load_balancing_port: port}
105+
106+
set_private(app, :__hash__, hash(app))
107+
end
108+
102109
def private_docker_registry?(%__MODULE__{docker_registry: nil}) do
103110
false
104111
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule Makina.MixProject do
44
def project do
55
[
66
app: :makina,
7-
version: "0.1.0",
7+
version: "0.1.11",
88
elixir: "~> 1.18",
99
start_permanent: Mix.env() == :prod,
1010
deps: deps(),

test/makina/dsl_test.exs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,29 @@ defmodule Makina.DSLTest do
172172
assert port == %{internal: 80, external: 8080}
173173
end
174174

175+
test "lets the user expose an app with domains" do
176+
import Makina.DSL
177+
178+
term =
179+
makina "app-test-specify-domain" do
180+
standalone do
181+
app name: "test" do
182+
publish_on_domain(["example.com"], from_port: 80)
183+
end
184+
end
185+
end
186+
187+
module = elem(term, 1)
188+
context = module.collect_context()
189+
190+
app = List.first(context.standalone_applications)
191+
192+
assert is_list(app.domains)
193+
194+
assert app.domains == ["example.com"]
195+
assert app.load_balancing_port == 80
196+
end
197+
175198
test "raises if app block is not invoked correctly" do
176199
import DSL
177200

test/makina/models/application_test.exs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,27 @@ defmodule Makina.Models.ApplicationTest do
110110
app = Application.new(params)
111111
init_hash = app.__hash__
112112

113-
assert app.env_vars == []
114-
115113
app = app |> Application.put_domain("example.com")
116114

117115
assert app.domains == ["example.com"]
118116
assert app.__hash__ != init_hash
119117
end
120118
end
121119

120+
describe "set_load_balancing_port/2" do
121+
test "sets the port used by the proxy load balancer" do
122+
params = [name: "foo"]
123+
124+
app = Application.new(params)
125+
init_hash = app.__hash__
126+
127+
app = app |> Application.set_load_balancing_port(8080)
128+
129+
assert app.load_balancing_port == 8080
130+
assert app.__hash__ != init_hash
131+
end
132+
end
133+
122134
describe "set_private/3" do
123135
test "sets private fields" do
124136
params = [name: "foo"]
@@ -130,7 +142,8 @@ defmodule Makina.Models.ApplicationTest do
130142

131143
assert app.__docker__ == %{
132144
command: [],
133-
labels: []
145+
labels: [],
146+
networks: []
134147
}
135148

136149
app = Application.set_private(app, :__scope__, [:foo])

test/makina/models/docker_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ defmodule Makina.Models.DockerTest do
2020

2121
assert is_struct(cmd, Makina.Command)
2222

23-
assert cmd.cmd == "docker inspect foo"
23+
assert cmd.cmd == "docker inspect --type=container foo"
2424
assert cmd.server == server
2525
end
2626
end
@@ -129,13 +129,13 @@ defmodule Makina.Models.DockerTest do
129129
app =
130130
Application.new(name: "foo")
131131
|> Application.set_docker_image(name: "nginx", tag: "1.16")
132-
|> Application.put_exposed_port(internal: 80, external: 8080)
133132
|> Application.put_domain("example.com")
133+
|> Application.set_load_balancing_port(80)
134134

135135
cmd = Docker.run(server, app)
136136

137137
assert cmd.cmd ==
138-
"docker run -d --restart unless-stopped --name foo --label org.makina.app.hash=#{app.__hash__} --label traefik.enable=true --label traefik.http.middlewares.foo.compress=true --label traefik.http.routers.foo.rule=Host\\(\\`example.com\\`\\) --label traefik.http.routers.foo.tls.certresolver=letsencrypt --label traefik.http.services.foo.loadBalancer.server.port=8080 -p 8080:80 nginx:1.16"
138+
"docker run -d --restart unless-stopped --name foo --label org.makina.app.hash=#{app.__hash__} --label traefik.enable=true --label traefik.http.middlewares.foo.compress=true --label traefik.http.routers.foo.rule=\"Host(\\`example.com\\`)\" --label traefik.http.routers.foo.tls.certresolver=letsencrypt --label traefik.http.services.foo.loadBalancer.server.port=80 --network makina-web-net nginx:1.16"
139139
end
140140
end
141141

0 commit comments

Comments
 (0)