Skip to content

Commit

Permalink
Add new option to manually expose the DNS port to the host, remove bi…
Browse files Browse the repository at this point in the history
…nding by default (#11011)
  • Loading branch information
dfangl committed Jun 12, 2024
1 parent cb18571 commit 64723d0
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 69 deletions.
8 changes: 8 additions & 0 deletions localstack-core/localstack/cli/localstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,13 @@ def _print_service_table(services: Dict[str, str]) -> None:
multiple=True,
required=False,
)
@click.option(
"--host-dns",
help="Expose the LocalStack DNS server to the host using port bindings.",
required=False,
is_flag=True,
default=False,
)
@publish_invocation
def cmd_start(
docker: bool,
Expand All @@ -467,6 +474,7 @@ def cmd_start(
env: Tuple = (),
publish: Tuple = (),
volume: Tuple = (),
host_dns: bool = False,
) -> None:
"""
Start the LocalStack runtime.
Expand Down
31 changes: 6 additions & 25 deletions localstack-core/localstack/utils/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
VolumeMappings,
)
from localstack.utils.container_utils.docker_cmd_client import CmdDockerClient
from localstack.utils.docker_utils import DOCKER_CLIENT, container_ports_can_be_bound
from localstack.utils.docker_utils import DOCKER_CLIENT
from localstack.utils.files import cache_dir, mkdir
from localstack.utils.functions import call_safe
from localstack.utils.net import Port, get_free_tcp_port, get_free_tcp_port_range
from localstack.utils.net import get_free_tcp_port, get_free_tcp_port_range
from localstack.utils.run import is_command_available, run, to_str
from localstack.utils.serving import Server
from localstack.utils.strings import short_uid
Expand Down Expand Up @@ -548,28 +548,6 @@ def _cfg(cfg: ContainerConfiguration):

return _cfg

@staticmethod
def publish_dns_ports(cfg: ContainerConfiguration):
dns_ports = [
Port(config.DNS_PORT, protocol="udp"),
Port(config.DNS_PORT, protocol="tcp"),
]
if container_ports_can_be_bound(dns_ports, address=config.DNS_ADDRESS):
# expose the DNS server to the host
# TODO: update ContainerConfiguration to support multiple PortMappings objects with different bind addresses
docker_flags = []
for port in dns_ports:
docker_flags.extend(
[
"-p",
f"{config.DNS_ADDRESS}:{port.port}:{port.port}/{port.protocol}",
]
)
if cfg.additional_flags is None:
cfg.additional_flags = " ".join(docker_flags)
else:
cfg.additional_flags += " " + " ".join(docker_flags)

@staticmethod
def container_name(name: str):
def _cfg(cfg: ContainerConfiguration):
Expand Down Expand Up @@ -693,6 +671,10 @@ def _cfg(cfg: ContainerConfiguration):
if params.get("network"):
cfg.network = params.get("network")

if params.get("host_dns"):
cfg.ports.add(config.DNS_PORT, config.DNS_PORT, "udp")
cfg.ports.add(config.DNS_PORT, config.DNS_PORT, "tcp")

# processed parsed -e, -p, and -v flags
ContainerConfigurators.env_cli_params(params.get("env"))(cfg)
ContainerConfigurators.port_cli_params(params.get("publish"))(cfg)
Expand Down Expand Up @@ -1186,7 +1168,6 @@ def configure_container(container: Container):
ContainerConfigurators.service_port_range,
ContainerConfigurators.mount_localstack_volume(config.VOLUME_DIR),
ContainerConfigurators.mount_docker_socket,
ContainerConfigurators.publish_dns_ports,
# overwrites any env vars set in the config that were previously set by configurators
ContainerConfigurators.config_env_vars,
# ensure that GATEWAY_LISTEN is taken from the config and not
Expand Down
51 changes: 7 additions & 44 deletions tests/cli/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import json
import logging
import os.path
import socket
from contextlib import closing

import pytest
import requests
Expand All @@ -12,13 +10,13 @@
from localstack import config, constants
from localstack.cli.localstack import localstack as cli
from localstack.config import Directories, in_docker
from localstack.constants import LOCALHOST_HOSTNAME, LOCALHOST_IP, MODULE_MAIN_PATH, TRUE_STRINGS
from localstack.constants import MODULE_MAIN_PATH, TRUE_STRINGS
from localstack.utils import bootstrap
from localstack.utils.bootstrap import in_ci
from localstack.utils.common import poll_condition
from localstack.utils.container_utils.container_client import ContainerClient, NoSuchImage
from localstack.utils.files import mkdir
from localstack.utils.net import get_free_udp_port, send_dns_query
from localstack.utils.net import get_free_udp_port
from localstack.utils.run import run, to_str

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -238,61 +236,26 @@ def test_start_cli_within_container(self, runner, container_client, tmp_path):

@pytest.mark.skipif(condition=in_docker(), reason="cannot run CLI tests in docker")
class TestDNSServer:
def test_dns_port_published(self, runner, container_client, monkeypatch):
def test_dns_port_published_with_flag(self, runner, container_client, monkeypatch):
port = get_free_udp_port()
monkeypatch.setenv("DEBUG", "1")
monkeypatch.setenv("DNS_PORT", str(port))
monkeypatch.setattr(config, "DNS_PORT", port)

runner.invoke(cli, ["start", "-d"])
runner.invoke(cli, ["start", "-d", "--host-dns"])
runner.invoke(cli, ["wait", "-t", "60"])

inspect = container_client.inspect_container(config.MAIN_CONTAINER_NAME)
assert f"{port}/udp" in inspect["HostConfig"]["PortBindings"]

@pytest.mark.skip(reason="For this change, the tests run on the previous image")
def test_dns_server_custom_port(self, runner, container_client, monkeypatch):
port = get_free_udp_port()
def test_dns_port_not_published_by_default(self, runner, container_client, monkeypatch):
monkeypatch.setenv("DEBUG", "1")
monkeypatch.setenv("DNS_PORT", str(port))
monkeypatch.setattr(config, "DNS_PORT", port)

runner.invoke(cli, ["start", "-d"])
runner.invoke(cli, ["wait", "-t", "60"])

reply = send_dns_query(name=LOCALHOST_HOSTNAME, port=port)
assert str(reply.a.rdata) == LOCALHOST_IP

@pytest.mark.skip(reason="For this change, the tests run on the previous image")
def test_dns_server_starts_if_host_port_bound(
self, runner, container_client, monkeypatch, dns_query_from_container
):
port = get_free_udp_port()
monkeypatch.setenv("DEBUG", "1")
monkeypatch.setenv("DNS_PORT", str(port))
monkeypatch.setattr(config, "DNS_PORT", port)

# bind the port
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("", port))

with closing(s):
runner.invoke(cli, ["start", "-d"])
runner.invoke(cli, ["wait", "-t", "60"])

inspect = container_client.inspect_container(config.MAIN_CONTAINER_NAME)
assert f"{port}/udp" not in inspect["HostConfig"]["PortBindings"]
ip_address = list(inspect["NetworkSettings"]["Networks"].values())[0]["IPAddress"]

with pytest.raises(TimeoutError):
send_dns_query(name=LOCALHOST_HOSTNAME, port=port)

# use a docker container to test the DNS
stdout, _ = dns_query_from_container(
name=LOCALHOST_HOSTNAME, ip_address=ip_address, port=port
)
assert ip_address in stdout.decode().splitlines()
inspect = container_client.inspect_container(config.MAIN_CONTAINER_NAME)
assert "53/udp" not in inspect["HostConfig"]["PortBindings"]


class TestHooks:
Expand Down

0 comments on commit 64723d0

Please sign in to comment.