Page MenuHomeVyOS Platform

containers.py
No OneTemporary

Size
8 KB
Referenced Files
None
Subscribers
None

containers.py

#!/usr/bin/env python3
#
# Copyright (C) 2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import json
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.util import cmd
from vyos.util import popen
from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
config_containers_registry = '/etc/containers/registries.conf'
config_containers_storage = '/etc/containers/storage.conf'
def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
print(command)
return cmd(command)
# Container management functions
def container_exists(name):
'''
https://docs.podman.io/en/latest/_static/api.html#operation/ContainerExistsLibpod
Check if container exists. Response codes.
204 - container exists
404 - no such container
'''
tmp = _cmd(f"curl --unix-socket /run/podman/podman.sock 'http://d/v3.0.0/libpod/containers/{name}/exists'")
# If container exists it return status code "0" - code can not be displayed
return (tmp == "")
def container_status(name):
'''
https://docs.podman.io/en/latest/_static/api.html#operation/ContainerInspectLibpod
'''
tmp = _cmd(f"curl --unix-socket /run/podman/podman.sock 'http://d/v3.0.0/libpod/containers/{name}/json'")
data = json.loads(tmp)
return data['State']['Status']
def ctnr_network_exists(name):
# Check explicit name for network, returns True if network exists
c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
return bool(c)
# Common functions
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
base = ['container']
container = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
container = dict_merge(default_values, container)
# Delete container network, delete containers
tmp = node_changed(conf, ['container', 'network'])
if tmp: container.update({'net_remove' : tmp})
tmp = node_changed(conf, ['container', 'name'])
if tmp: container.update({'container_remove' : tmp})
return container
def verify(container):
# bail out early - looks like removal from running config
if not container:
return None
# Add new container
if 'name' in container:
for name, container_config in container['name'].items():
if 'network' in container_config:
if len(container_config['network']) > 1:
raise ConfigError(f'Only one network can be specified for container "{name}"!')
# Check if the specified container network exists
network_name = list(container_config['network'])[0]
if network_name not in container_config['network']:
raise ConfigError('Container network "{network_name}" does not exist!')
# Container image is a mandatory option
if 'image' not in container_config:
raise ConfigError(f'Container image for "{name}" is mandatory!')
# If 'allow-host-networks' or 'network' not set.
if 'allow_host_networks' not in container_config and 'network' not in container_config:
raise ConfigError(f'Must either set "network" or "allow-host-networks" for container "{name}"!')
# Can not set both allow-host-networks and network at the same time
if {'allow_host_networks', 'network'} <= set(container_config):
raise ConfigError(f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
# Add new network
if 'network' in container:
v4_prefix = 0
v6_prefix = 0
for network, network_config in container['network'].items():
# If ipv4-prefix not defined for user-defined network
if 'prefix' not in network_config:
raise ConfigError(f'prefix for network "{net}" must be defined!')
for prefix in network_config['prefix']:
if is_ipv4(prefix): v4_prefix += 1
elif is_ipv6(prefix): v6_prefix += 1
if v4_prefix > 1:
raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
if v6_prefix > 1:
raise ConfigError(f'Only one IPv6 prefix can be defined for network "{network}"!')
# A network attached to a container can not be deleted
if {'net_remove', 'name'} <= set(container):
for network in container['net_remove']:
for container, container_config in container['name'].items():
if 'network' in container_config and network in container_config['network']:
raise ConfigError(f'Can not remove network "{network}", used by container "{container}"!')
return None
def generate(container):
# bail out early - looks like removal from running config
if not container:
return None
render(config_containers_registry, 'containers/registry.tmpl', container)
render(config_containers_storage, 'containers/storage.tmpl', container)
return None
def apply(container):
# Delete old containers if needed. We can't delete running container
# Option "--force" allows to delete containers with any status
if 'container_remove' in container:
for name in container['container_remove']:
if container_status(name) == 'running':
_cmd(f'podman stop {name}')
_cmd(f'podman rm --force {name}')
# Delete old networks if needed
if 'net_remove' in container:
for network in container['net_remove']:
_cmd(f'podman network rm {network}')
# Add network
if 'network' in container:
for network, network_config in container['network'].items():
# Check if the network has already been created
if not ctnr_network_exists(network) and 'prefix' in network_config:
tmp = f'podman network create {network}'
# we can not use list comprehension here as the --ipv6 option
# must immediately follow the specified subnet!!!
for prefix in sorted(network_config['prefix']):
tmp += f' --subnet={prefix}'
if is_ipv6(prefix):
tmp += ' --ipv6'
_cmd(tmp)
# Add container
if 'name' in container:
for name, container_config in container['name'].items():
# Check if the container has already been created
if not container_exists(name):
image = container_config['image']
# Currently the best way to run a command and immediately print stdout
print(os.system(f'podman pull {image}'))
# Check/set environment options "-e foo=bar"
env_opt = ''
if 'environment' in container_config:
env_opt = '-e '
env_opt += " -e ".join(f"{k}={v['value']}" for k, v in container_config['environment'].items())
if 'allow_host_networks' in container_config:
_cmd(f'podman run -dit --name {name} --net host {env_opt} {image}')
else:
for network in container_config['network']:
ipparam = ''
if 'address' in container_config['network'][network]:
ipparam = '--ip ' + container_config['network'][network]['address']
_cmd(f'podman run --name {name} -dit --net {network} {ipparam} {env_opt} {image}')
# Else container is already created. Just start it.
# It's needed after reboot.
elif container_status(name) != 'running':
_cmd(f'podman start {name}')
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
exit(1)

File Metadata

Mime Type
text/x-script.python
Expires
Sat, Oct 17, 12:49 AM (20 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4353676
Default Alt Text
containers.py (8 KB)

Event Timeline