Skip to content

Commit aa5676e

Browse files
authored
Merge pull request #27 from egolus/network_alias
add the ability to set aliases per network
2 parents 5731c62 + 4f41a0f commit aa5676e

4 files changed

Lines changed: 179 additions & 19 deletions

File tree

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,29 @@
11
name: Claude Code Review
22

3+
# pull_request_target runs in the context of the BASE repo (not the fork),
4+
# giving access to secrets even for PRs from forks.
5+
# Security note: the checkout below intentionally uses the base branch ref,
6+
# NOT the fork's code, to avoid executing untrusted code with repo secrets.
37
on:
4-
pull_request:
8+
pull_request_target:
59
types: [opened, synchronize, ready_for_review, reopened]
6-
# Optional: Only run on specific file changes
7-
# paths:
8-
# - "src/**/*.ts"
9-
# - "src/**/*.tsx"
10-
# - "src/**/*.js"
11-
# - "src/**/*.jsx"
1210

1311
jobs:
1412
claude-review:
15-
# Optional: Filter by PR author
16-
# if: |
17-
# github.event.pull_request.user.login == 'external-contributor' ||
18-
# github.event.pull_request.user.login == 'new-developer' ||
19-
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20-
2113
runs-on: ubuntu-latest
2214
permissions:
2315
contents: read
24-
pull-requests: read
25-
issues: read
16+
pull-requests: write
17+
issues: write
2618
id-token: write
2719

2820
steps:
29-
- name: Checkout repository
21+
- name: Checkout base repository
3022
uses: actions/checkout@v4
3123
with:
24+
# Explicitly checkout the base branch, not the fork's code.
25+
# This is required when using pull_request_target with fork PRs.
26+
ref: ${{ github.event.pull_request.base.sha }}
3227
fetch-depth: 1
3328

3429
- name: Run Claude Code Review
@@ -41,4 +36,3 @@ jobs:
4136
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
4237
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
4338
# or https://code.claude.com/docs/en/cli-reference for available options
44-

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,25 @@ labels:
106106
> A network named `frontend` in a project folder `myapp` becomes
107107
> `myapp_frontend`.
108108

109+
### Network aliases
110+
111+
If a container has the label `traefik.aliases` these aliases are set for
112+
traefik when it is added to the network. `traefik.aliases` must be a
113+
comma-separated list. Having aliases on traefik is useful to separate stacks
114+
but still letting a container from one stack talk through traefik to the other
115+
stack.
116+
117+
```yaml
118+
labels:
119+
- "traefik.enable=true"
120+
- "traefik.aliases=myotherapp.domain"
121+
- "traefik.docker.network=myapp_frontend"
122+
- "traefik.http.routers.myapp.rule=Host(`myapp.${DOMAIN}`)"
123+
- "traefik.http.routers.myapp.entrypoints=websecure"
124+
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
125+
- "traefik.http.services.myapp.loadbalancer.server.port=8080"
126+
```
127+
109128
---
110129
111130
## ⚙️ Configuration

main.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ def connect_traefik_to_network(container):
106106
# Retrieve allowed networks from the container's labels, if specified
107107
allowed_networks_label = container.labels.get(config.traefik.networkLabel, "")
108108
allowed_networks = allowed_networks_label.split(",")
109-
109+
110+
# add alias labels
111+
alias_label = container.labels.get("traefik.aliases")
112+
aliases = [a.strip() for a in alias_label.split(",") if a.strip()] if alias_label else []
113+
110114
app_logger.debug(f"Allowed networks: {allowed_networks}")
111115

112116
for net in target_networks:
@@ -130,7 +134,15 @@ def connect_traefik_to_network(container):
130134
if allowed_networks == [''] or net in allowed_networks:
131135
if net not in traefik_container.attrs["NetworkSettings"]["Networks"]:
132136
app_logger.debug(f"Connecting Traefik to network {net}.")
133-
network.connect(traefik_container)
137+
# We are already inside the allowed-networks guard above, so the
138+
# aliases must be applied whenever they are set, including the
139+
# default case where no allowed-networks label is provided
140+
# (allowed_networks == ['']). An extra net membership check here
141+
# would silently drop aliases in that default case.
142+
if aliases:
143+
network.connect(traefik_container, aliases=aliases)
144+
else:
145+
network.connect(traefik_container)
134146
app_logger.info(f"Successfully connected Traefik to network {net}.")
135147
else:
136148
app_logger.info(f"Traefik is already connected to network {net}, skipping connection.")

tests/unit/test_main_logic.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,140 @@ def test_compose_network_resolution(self, mock_docker_client, mock_config, mock_
251251

252252
network.connect.assert_called_once_with(traefik)
253253

254+
def test_no_alias_set(self, mock_docker_client, mock_config, mock_logger):
255+
"""
256+
When traefik.aliases is not set, the container should not get an alias
257+
"""
258+
container = MagicMock()
259+
container.name = "web-app"
260+
container.attrs = {
261+
"NetworkSettings": {"Networks": {"app_net": {}}}
262+
}
263+
container.labels = {}
264+
265+
traefik = MagicMock()
266+
traefik.attrs = {"NetworkSettings": {"Networks": {"bridge": {}}}}
267+
mock_docker_client.containers.get.return_value = traefik
268+
269+
network = MagicMock()
270+
network.attrs = {"Name": "app_net", "Labels": {}}
271+
mock_docker_client.networks.get.return_value = network
272+
273+
main.connect_traefik_to_network(container)
274+
275+
network.connect.assert_called_once()
276+
assert("aliases" not in network.connect.call_args.kwargs.keys())
277+
278+
def test_single_alias_set(self, mock_docker_client, mock_config, mock_logger):
279+
"""
280+
When traefik.aliases is set to a single value, the network connect call
281+
should have that value as alias
282+
"""
283+
container = MagicMock()
284+
container.name = "web-app"
285+
container.attrs = {
286+
"NetworkSettings": {"Networks": {"app_net": {}}}
287+
}
288+
container.labels = {
289+
"traefik.aliases": "app",
290+
"traefik.docker.network": "app_net",
291+
}
292+
293+
traefik = MagicMock()
294+
traefik.attrs = {"NetworkSettings": {"Networks": {"bridge": {}}}}
295+
mock_docker_client.containers.get.return_value = traefik
296+
297+
network = MagicMock()
298+
network.attrs = {"Name": "app_net", "Labels": {}}
299+
mock_docker_client.networks.get.return_value = network
300+
301+
main.connect_traefik_to_network(container)
302+
303+
network.connect.assert_called_once_with(traefik, aliases=["app"])
304+
305+
def test_multiple_aliases_set(self, mock_docker_client, mock_config, mock_logger):
306+
"""
307+
When traefik.aliases is set to a comma separated string, the network
308+
connect call should have a list of strings value as aliases value
309+
"""
310+
container = MagicMock()
311+
container.name = "web-app"
312+
container.attrs = {
313+
"NetworkSettings": {"Networks": {"app_net": {}}}
314+
}
315+
container.labels = {
316+
"traefik.aliases": "app, app2, app3",
317+
"traefik.docker.network": "app_net",
318+
}
319+
320+
traefik = MagicMock()
321+
traefik.attrs = {"NetworkSettings": {"Networks": {"bridge": {}}}}
322+
mock_docker_client.containers.get.return_value = traefik
323+
324+
network = MagicMock()
325+
network.attrs = {"Name": "app_net", "Labels": {}}
326+
mock_docker_client.networks.get.return_value = network
327+
328+
main.connect_traefik_to_network(container)
329+
330+
network.connect.assert_called_once_with(
331+
traefik, aliases=["app", "app2", "app3"])
332+
333+
def test_aliases_with_additional_whitespace(self, mock_docker_client, mock_config, mock_logger):
334+
"""
335+
When traefik.aliases is set to a comma separated string, the network
336+
connect call should have a list of strings value as aliases value
337+
"""
338+
container = MagicMock()
339+
container.name = "web-app"
340+
container.attrs = {
341+
"NetworkSettings": {"Networks": {"app_net": {}}}
342+
}
343+
container.labels = {
344+
"traefik.aliases": " app one, app2, app3 ",
345+
"traefik.docker.network": "app_net",
346+
}
347+
348+
traefik = MagicMock()
349+
traefik.attrs = {"NetworkSettings": {"Networks": {"bridge": {}}}}
350+
mock_docker_client.containers.get.return_value = traefik
351+
352+
network = MagicMock()
353+
network.attrs = {"Name": "app_net", "Labels": {}}
354+
mock_docker_client.networks.get.return_value = network
355+
356+
main.connect_traefik_to_network(container)
357+
358+
network.connect.assert_called_once_with(
359+
traefik, aliases=["app one", "app2", "app3"])
360+
361+
def test_alias_set_without_network_label(self, mock_docker_client, mock_config, mock_logger):
362+
"""
363+
Regression: when traefik.aliases is set but no allowed-networks label is
364+
provided (all networks allowed by default), the aliases must still be
365+
applied to the network connect call.
366+
"""
367+
container = MagicMock()
368+
container.name = "web-app"
369+
container.attrs = {
370+
"NetworkSettings": {"Networks": {"app_net": {}}}
371+
}
372+
container.labels = {
373+
"traefik.aliases": "app",
374+
}
375+
376+
traefik = MagicMock()
377+
traefik.attrs = {"NetworkSettings": {"Networks": {"bridge": {}}}}
378+
mock_docker_client.containers.get.return_value = traefik
379+
380+
network = MagicMock()
381+
network.attrs = {"Name": "app_net", "Labels": {}}
382+
mock_docker_client.networks.get.return_value = network
383+
384+
main.connect_traefik_to_network(container)
385+
386+
network.connect.assert_called_once_with(traefik, aliases=["app"])
387+
254388

255389
# ---------------------------------------------------------------------------
256390
# disconnect_traefik_from_network
@@ -462,3 +596,4 @@ def get_container(name):
462596

463597
# connect should have been called for both containers' networks
464598
assert network.connect.call_count == 2
599+

0 commit comments

Comments
 (0)