summaryrefslogtreecommitdiff
path: root/src/conf_mode/container.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode/container.py')
-rwxr-xr-xsrc/conf_mode/container.py142
1 files changed, 86 insertions, 56 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 68070ea5b..05595f86f 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -16,6 +16,7 @@
import os
+from hashlib import sha256
from ipaddress import ip_address
from ipaddress import ip_network
from json import dumps as json_write
@@ -24,6 +25,9 @@ from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_vrf
+from vyos.ifconfig import Interface
from vyos.util import call
from vyos.util import cmd
from vyos.util import run
@@ -38,8 +42,9 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-config_containers_registry = '/etc/containers/registries.conf'
-config_containers_storage = '/etc/containers/storage.conf'
+config_containers = '/etc/containers/containers.conf'
+config_registry = '/etc/containers/registries.conf'
+config_storage = '/etc/containers/storage.conf'
systemd_unit_path = '/run/systemd/system'
def _cmd(command):
@@ -83,6 +88,15 @@ def get_config(config=None):
for name in container['name']:
container['name'][name] = dict_merge(default_values, container['name'][name])
+ # T5047: Any container related configuration changed? We only
+ # wan't to restart the required containers and not all of them ...
+ tmp = is_node_changed(conf, base + ['name', name])
+ if tmp:
+ if 'container_restart' not in container:
+ container['container_restart'] = [name]
+ else:
+ container['container_restart'].append(name)
+
# XXX: T2665: we can not safely rely on the defaults() when there are
# tagNodes in place, it is better to blend in the defaults manually.
if 'port' in container['name'][name]:
@@ -154,21 +168,29 @@ def verify(container):
raise ConfigError(f'Container network "{network_name}" does not exist!')
if 'address' in container_config['network'][network_name]:
- address = container_config['network'][network_name]['address']
- network = None
- if is_ipv4(address):
- network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0]
- elif is_ipv6(address):
- network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0]
-
- # Specified container IP address must belong to network prefix
- if ip_address(address) not in ip_network(network):
- raise ConfigError(f'Used container address "{address}" not in network "{network}"!')
-
- # We can not use the first IP address of a network prefix as this is used by podman
- if ip_address(address) == ip_network(network)[1]:
- raise ConfigError(f'IP address "{address}" can not be used for a container, '\
- 'reserved for the container engine!')
+ cnt_ipv4 = 0
+ cnt_ipv6 = 0
+ for address in container_config['network'][network_name]['address']:
+ network = None
+ if is_ipv4(address):
+ network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0]
+ cnt_ipv4 += 1
+ elif is_ipv6(address):
+ network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0]
+ cnt_ipv6 += 1
+
+ # Specified container IP address must belong to network prefix
+ if ip_address(address) not in ip_network(network):
+ raise ConfigError(f'Used container address "{address}" not in network "{network}"!')
+
+ # We can not use the first IP address of a network prefix as this is used by podman
+ if ip_address(address) == ip_network(network)[1]:
+ raise ConfigError(f'IP address "{address}" can not be used for a container, '\
+ 'reserved for the container engine!')
+
+ if cnt_ipv4 > 1 or cnt_ipv6 > 1:
+ raise ConfigError(f'Only one IP address per address family can be used for '\
+ f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!')
if 'device' in container_config:
for dev, dev_config in container_config['device'].items():
@@ -230,6 +252,8 @@ def verify(container):
if v6_prefix > 1:
raise ConfigError(f'Only one IPv6 prefix can be defined for network "{network}"!')
+ # Verify VRF exists
+ verify_vrf(network_config)
# A network attached to a container can not be deleted
if {'network_remove', 'name'} <= set(container):
@@ -238,9 +262,11 @@ def verify(container):
if 'network' in container_config and network in container_config['network']:
raise ConfigError(f'Can not remove network "{network}", used by container "{container}"!')
- if 'registry' in container and 'authentication' in container['registry']:
- for registry, registry_config in container['registry']['authentication'].items():
- if not {'username', 'password'} <= set(registry_config):
+ if 'registry' in container:
+ for registry, registry_config in container['registry'].items():
+ if 'authentication' not in registry_config:
+ continue
+ if not {'username', 'password'} <= set(registry_config['authentication']):
raise ConfigError('If registry username or or password is defined, so must be the other!')
return None
@@ -326,51 +352,47 @@ def generate_run_arguments(name, container_config):
ip_param = ''
networks = ",".join(container_config['network'])
for network in container_config['network']:
- if 'address' in container_config['network'][network]:
- address = container_config['network'][network]['address']
- ip_param = f'--ip {address}'
+ if 'address' not in container_config['network'][network]:
+ continue
+ for address in container_config['network'][network]['address']:
+ if is_ipv6(address):
+ ip_param += f' --ip6 {address}'
+ else:
+ ip_param += f' --ip {address}'
return f'{container_base_cmd} --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
def generate(container):
# bail out early - looks like removal from running config
if not container:
- if os.path.exists(config_containers_registry):
- os.unlink(config_containers_registry)
- if os.path.exists(config_containers_storage):
- os.unlink(config_containers_storage)
+ for file in [config_containers, config_registry, config_storage]:
+ if os.path.exists(file):
+ os.unlink(file)
return None
if 'network' in container:
for network, network_config in container['network'].items():
tmp = {
- 'cniVersion' : '0.4.0',
- 'name' : network,
- 'plugins' : [{
- 'type': 'bridge',
- 'bridge': f'cni-{network}',
- 'isGateway': True,
- 'ipMasq': False,
- 'hairpinMode': False,
- 'ipam' : {
- 'type': 'host-local',
- 'routes': [],
- 'ranges' : [],
- },
- }]
+ 'name': network,
+ 'id' : sha256(f'{network}'.encode()).hexdigest(),
+ 'driver': 'bridge',
+ 'network_interface': f'podman-{network}',
+ 'subnets': [],
+ 'ipv6_enabled': False,
+ 'internal': False,
+ 'dns_enabled': False,
+ 'ipam_options': {
+ 'driver': 'host-local'
+ }
}
-
for prefix in network_config['prefix']:
- net = [{'gateway' : inc_ip(prefix, 1), 'subnet' : prefix}]
- tmp['plugins'][0]['ipam']['ranges'].append(net)
+ net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)}
+ tmp['subnets'].append(net)
- # install per address-family default orutes
- default_route = '0.0.0.0/0'
if is_ipv6(prefix):
- default_route = '::/0'
- tmp['plugins'][0]['ipam']['routes'].append({'dst': default_route})
+ tmp['ipv6_enabled'] = True
- write_file(f'/etc/cni/net.d/{network}.conflist', json_write(tmp, indent=2))
+ write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2))
if 'registry' in container:
cmd = f'podman logout --all'
@@ -390,8 +412,9 @@ def generate(container):
if rc != 0:
raise ConfigError(out)
- render(config_containers_registry, 'container/registries.conf.j2', container)
- render(config_containers_storage, 'container/storage.conf.j2', container)
+ render(config_containers, 'container/containers.conf.j2', container)
+ render(config_registry, 'container/registries.conf.j2', container)
+ render(config_storage, 'container/storage.conf.j2', container)
if 'name' in container:
for name, container_config in container['name'].items():
@@ -420,10 +443,7 @@ def apply(container):
# Delete old networks if needed
if 'network_remove' in container:
for network in container['network_remove']:
- call(f'podman network rm {network}')
- tmp = f'/etc/cni/net.d/{network}.conflist'
- if os.path.exists(tmp):
- os.unlink(tmp)
+ call(f'podman network rm {network} >/dev/null 2>&1')
# Add container
disabled_new = False
@@ -447,11 +467,21 @@ def apply(container):
os.unlink(file_path)
continue
- cmd(f'systemctl restart vyos-container-{name}.service')
+ if 'container_restart' in container and name in container['container_restart']:
+ cmd(f'systemctl restart vyos-container-{name}.service')
if disabled_new:
call('systemctl daemon-reload')
+ # Start network and assign it to given VRF if requested. this can only be done
+ # after the containers got started as the podman network interface will
+ # only be enabled by the first container and yet I do not know how to enable
+ # the network interface in advance
+ if 'network' in container:
+ for network, network_config in container['network'].items():
+ tmp = Interface(f'podman-{network}')
+ tmp.set_vrf(network_config.get('vrf', ''))
+
return None
if __name__ == '__main__':