Skip to content

Commit

Permalink
Update modules, typing, seperate dcdc-ips
Browse files Browse the repository at this point in the history
- Replace `netifaces` with `ifaddr` as [`netifaces` is now
  unmaintained](al45tair/netifaces#78).
- Fix various linting issues
- Seperate `dcdc --ips` into `dcdc-ips` command
- Add `nserver` to logging
- Add more log calls
  • Loading branch information
nhairs committed May 11, 2024
1 parent 4ac6f6b commit 9dadbdc
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 43 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,16 @@ dig some.container.dcdc

## Usage

The main application is provided by the `dcdc` command.

```
usage: dcdc [-h] [-c CONFIG_PATH] [-v] [--log-dir PATH] [--version] [--host HOST] [--port PORT]
[--tcp | --udp] [--root-domain ROOT_DOMAIN] [--ips]
usage: dcdc [-h] [-v] [--log-dir PATH] [--version] [--host HOST] [--port PORT] [--tcp | --udp]
[--root-domain ROOT_DOMAIN]
dcdc (Docker Container Domain Connector) is a dns server that allows mapping docker containers to their currently running bridge ip address.
options:
-h, --help show this help message and exit
-c CONFIG_PATH, --config CONFIG_PATH
Add a config file to parse. Config files are parsed in the order they are
added with values being merged into the previously parsed config.
-v, --verbose Increase logging verbosity
--log-dir PATH Set where log files should be stored. Defaults to /var/tmp
--version show program's version number and exit
Expand All @@ -91,9 +90,10 @@ options:
--root-domain ROOT_DOMAIN
Root domain for queries (e.g. <query>.<root>). Does not have to be a TLD, can
be any level of domain. Defaults to ".dcdc".
--ips Print available IPs and exit
```

This package also provies the `dcdc-ips` utility command which will print available IP addresses.

## Licence
This project is licenced under the MIT Licence - see [`LICENCE`](https://github.com/nhairs/docker-container-domain-connector/blob/main/LICENCE).

Expand Down
22 changes: 13 additions & 9 deletions pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ disable=raw-checker-failed,
# cases. Disable rules that can cause conflicts
line-too-long,
# Module docstrings are not required
missing-module-docstring
missing-module-docstring,
duplicate-code
## Project Disables

# Enable the message, report, category or checker with the given id(s). You can
Expand Down Expand Up @@ -227,8 +228,10 @@ good-names=i,
Run,
_,
e,
r,
id,
db
f,
ip,

# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
Expand Down Expand Up @@ -456,7 +459,8 @@ preferred-modules=
defining-attr-methods=__init__,
__new__,
setUp,
__post_init__
__post_init__,
setup,

# List of member names, which should be excluded from the protected access
# warning.
Expand All @@ -476,10 +480,10 @@ valid-metaclass-classmethod-first-arg=cls
[DESIGN]

# Maximum number of arguments for function / method.
max-args=5
max-args=10

# Maximum number of attributes for a class (see R0902).
max-attributes=7
max-attributes=15

# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
Expand All @@ -497,18 +501,18 @@ max-parents=7
max-public-methods=20

# Maximum number of return / yield for function / method body.
max-returns=6
max-returns=10

# Maximum number of statements in function / method body.
max-statements=50

# Minimum number of public methods for a class (see R0903).
min-public-methods=2
min-public-methods=1


[EXCEPTIONS]

# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=BaseException,
Exception
overgeneral-exceptions=builtins.BaseException,
builtins.Exception
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ authors = [
requires-python = ">=3.8"
dependencies = [
"docker",
"netifaces",
"ifaddr",
"nserver",
"pillar"
]
Expand Down Expand Up @@ -47,6 +47,8 @@ GitHub = "https://github.com/nhairs/docker-container-domain-connector"

[project.optional-dependencies]
dev = [
## Typing
"types-docker",
## Lint
"validate-pyproject[all]",
"black",
Expand All @@ -62,6 +64,7 @@ dev = [

[project.scripts]
dcdc = "dcdc.application:main"
dcdc-ips = "dcdc.application:ips_main"

[tool.setuptools.package-data]
dcdc = ["py.typed"]
Expand Down
74 changes: 47 additions & 27 deletions src/dcdc/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@
from __future__ import annotations

## Standard Library
import argparse
from dataclasses import dataclass
import logging
import sys
import time

## Installed
from docker import DockerClient
import netifaces
import ifaddr
import nserver
import pillar.application

Expand All @@ -21,37 +18,56 @@

### CLASSES
### ============================================================================
_APP = None
_APP: pillar.application.Application


### FUNCTIONS
### ============================================================================
def get_available_ips():
def get_available_ips() -> list[str]:
"""Get all available IPv4 Address on this machine."""
# Source: https://stackoverflow.com/a/274644
ip_list = []
for interface in netifaces.interfaces():
for link in netifaces.ifaddresses(interface).get(netifaces.AF_INET, []):
ip_list.append(link["addr"] + f" ({interface})")

ip_list: list[str] = []
for adaptor in ifaddr.get_adapters():
for _ip in adaptor.ips:
ip = _ip.ip
if isinstance(ip, str):
# IPv4
ip_list.append(f"{ip}\t({adaptor.nice_name})")
elif isinstance(ip, tuple):
# IPv6
# Currently only IPv4
pass
# ip_list.append(f"{ip[0]}]\t({adapter.nice_name})")
else:
raise ValueError(f"Unsupported IP: {ip!r}")

ip_list.sort()
# shortcut for all
ip_list.append("0.0.0.0 (all above)")
ip_list.append("0.0.0.0\t(all)")
return ip_list


def main(argv=None):
"""Main function for use with setup.py"""
"""Main function entrypoint for dcdc"""
global _APP # pylint: disable=global-statement

_APP = Application(argv)
exit_code = _APP.run()
return exit_code


def ips_main() -> None:
"""Main function entrypoint for dcdc-ips"""
print("\n".join(get_available_ips()))
return


### CLASSES
### ============================================================================
@dataclass
class CachedContainer:
"""Dataclass for caching container info"""

container_ids: dict[str, str]
container_name: str
project_name: str
Expand All @@ -77,19 +93,22 @@ class Application(pillar.application.Application):
)
)

def setup(self) -> None:
super().setup()
config_args_enabled = False
config_required = False

logging_manifest = pillar.application.LoggingManifest(additional_namespaces=["nserver"]) # type: ignore[call-arg]

def setup(self, *args, **kwargs) -> None:
super().setup(*args, **kwargs)
self.nserver = self.get_nserver()
self.container_cache: dict[str, CachedContainer] = {}
self.docker = DockerClient()
return

def main(self) -> None:
if self.args.ips:
print("\n".join(get_available_ips()))
return

self.info(f"Starting server on {self.args.transport} {self.args.host}:{self.args.port}")
self.nserver.run()
self.info("Shutting down")
return

def get_argument_parser(self):
Expand Down Expand Up @@ -134,9 +153,6 @@ def get_argument_parser(self):
help='Root domain for queries (e.g. <query>.<root>). Does not have to be a TLD, can be any level of domain. Defaults to ".dcdc".',
)

# Misc
parser.add_argument("--ips", action="store_true", help="Print available IPs and exit")

parser.set_defaults(transport="UDPv4")
return parser

Expand All @@ -147,7 +163,8 @@ def get_nserver(self) -> nserver.NameServer:
server.settings.SERVER_TYPE = self.args.transport
server.settings.SERVER_ADDRESS = self.args.host
server.settings.SERVER_PORT = self.args.port
server.settings.CONSOLE_LOG_LEVEL = logging.WARNING
server.settings.CONSOLE_LOG_LEVEL = 100
server.settings.FILE_LOG_LEVEL = 1

self.args.root_domain = self.args.root_domain.strip(".")
server.settings.ROOT_DOMAIN = self.args.root_domain
Expand All @@ -156,6 +173,8 @@ def get_nserver(self) -> nserver.NameServer:
return server

def attach_rules(self, server: nserver.NameServer) -> None:
"""Attach rules to the given nserver instance"""

@server.rule(f"*.*.{server.settings.ROOT_DOMAIN}", ["A", "AAAA"])
def compose_project_rule(query):
if query.name not in self.container_cache:
Expand Down Expand Up @@ -187,7 +206,8 @@ def compose_project_rule(query):

return

def populate_cache(self):
def populate_cache(self) -> None:
"""Populate self.container_cache"""
self.info("populating cache")
cache: dict[str, CachedContainer] = {}
# Get new entries
Expand All @@ -205,11 +225,11 @@ def populate_cache(self):
cached_container = cache.get(cache_key, None)
if cached_container is None:
cached_container = CachedContainer(
container_ids=dict(),
container_ids={},
container_name=container_name,
project_name=project_name,
ipv4_addresses=list(),
ipv6_addresses=list(),
ipv4_addresses=[],
ipv6_addresses=[],
last_updated=time.time(),
)
cache[cache_key] = cached_container
Expand Down

0 comments on commit 9dadbdc

Please sign in to comment.