confd: initial support for Docker containers using podman

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2024-02-25 19:49:27 +01:00
parent 0bda0727b7
commit 9e801dfa2f
15 changed files with 1121 additions and 9 deletions
@@ -0,0 +1 @@
local1.* -/var/log/container
+220
View File
@@ -0,0 +1,220 @@
#!/bin/sh
all=""
log()
{
logger -I $PPID -t container -p local1.notice -- "$*"
}
verify()
{
name=$1
for nm in $(podman ps -a --format "{{.Names}}"); do
if [ "$name" = "$nm" ]; then
return;
fi
done
>&2 echo "$name: no such container."
exit 1
}
# shellcheck disable=SC2086
create()
{
name=$1
image=$2
shift 2
if [ -z "$name" ] || [ -z "$image" ]; then
echo "Usage:"
echo " container create NAME IMAGE"
exit 1
fi
args="$args --restart=no --systemd=false --tz=local --privileged --replace --quiet"
# args="$args --log-opt tag=$name"
pidfn=/run/pod:${name}.pid
for net in "$@"; do
network="$network --net $net"
done
log "---------------------------------------"
log "Got name: $name image: $image"
log "Got networks: $network"
if [ -n "$network" ]; then
for srv in $dns; do
args="$args --dns=$srv"
done
for domain in $search; do
args="$args --dns-search=$domain"
done
else
network="--net none"
fi
# --syslog --log-level info
log "Calling \"podman create --name $name --conmon-pidfile=$pidfn $network $args $image\""
if podman create --name "$name" --conmon-pidfile="$pidfn" $network $args "$image"; then
log "Successfully created container $name from $image"
initctl -nbq cond set pod:system
exit 0
fi
log "Error: failed creating container $name, please check the configuration."
exit 1
}
delete()
{
name=$1
image=$2
if [ -z "$name" ]; then
echo "Usage:"
echo " container delete NAME"
exit 1
fi
podman kill -s KILL "$name" >/dev/null 2>&1
podman rm -vif "$name" 2>&1
log "Container $name has been removed."
}
usage()
{
cat <<EOF
usage:
container [opt] cmd [arg]
options:
-a, --all Show all, of something
--dns NAMESERVER Set nameserver(s) when creating a container
--dns-search LIST Set host lookup search list when creating container
-f, --force Force operation, e.g. remove
-h, --help Show this help text
-s, --simple Show output in simplified format
commands:
create NAME IMAGE NET Create container NAME using IMAGE with networks NET
delete NAME Kill and remove container NAME
help Show this help text
list [image] List names (only) of containers or images
remove IMAGE Remove an (unused) container image
run NAME CMD Execute a command (interactively) in container
show [image] Show containers or container images
start NAME Start a container
stop NAME Stop a container
EOF
}
while [ "$1" != "" ]; do
case $1 in
-a | --all)
all="-a"
;;
--dns)
shift
dns="$dns $1"
;;
--dns-search)
shift
search="$search $1"
;;
-f | --force)
force="-f"
;;
-h | --help)
usage
exit 0
;;
-s | --simple)
simple=true
;;
*)
break
;;
esac
shift
done
cmd=$1
if [ -n "$cmd" ]; then
shift
fi
case $cmd in
attach)
podman exec -it "$1" "$2"
;;
create)
log "Got create args: $*"
create "$@"
;;
delete)
delete "$@"
;;
help)
usage
;;
ls | list)
cmd=$1
shift
case $cmd in
image*)
podman images $all --format "{{.Repository}}:{{.Tag}}"
;;
*)
podman ps $all --format "{{.Names}}"
;;
esac
;;
pull)
podman pull "$@"
;;
remove)
podman rmi $all $force -i "$1"
;;
run)
echo "Starting container $1 :: use Ctrl-p Ctrl-q to exit"
podman run -it --rm "$@"
;;
show)
cmd=$1
shift
case $cmd in
image*)
if [ -n "$simple" ]; then
podman images $all --format "{{.Names}} {{.Size}}" \
| sed 's/\[\(.*\)\] /\1 /g' \
| awk '{ printf "%-60s %s %s\n", $1, $2, $3}'
else
podman images $all
fi
;;
*)
if [ -n "$simple" ]; then
podman ps $all --format "{{.ID}} {{.Names}} {{.Image}}" \
| awk '{ printf "%s %-30s %s\n", $1, $2, $3}'
else
podman ps $all
fi
;;
esac
;;
start)
verify "$1" || exit 1
initctl -bq start "pod:$1"
;;
stop)
verify "$1" || exit 1
initctl -bq stop "pod:$1" || podman kill "$1"
;;
*)
usage
exit 1
;;
esac
+5 -4
View File
@@ -34,7 +34,6 @@ BR2_LINUX_KERNEL_CUSTOM_DTS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/dt
BR2_LINUX_KERNEL_DTB_KEEP_DIRNAME=y
BR2_LINUX_KERNEL_INSTALL_TARGET=y
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y
BR2_PACKAGE_STRACE=y
BR2_PACKAGE_STRESS_NG=y
BR2_PACKAGE_JQ=y
@@ -54,7 +53,7 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y
BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_LIBSSH_OPENSSL=y
BR2_PACKAGE_LIBOPENSSL_BIN=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_NETOPEER2_CLI=y
@@ -71,7 +70,6 @@ BR2_PACKAGE_FPING=y
BR2_PACKAGE_FRR=y
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
@@ -89,7 +87,6 @@ BR2_PACKAGE_SOCAT=y
BR2_PACKAGE_TCPDUMP=y
BR2_PACKAGE_TRACEROUTE=y
BR2_PACKAGE_ULOGD=y
BR2_PACKAGE_BASH=y
BR2_PACKAGE_BASH_COMPLETION=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_HTOP=y
@@ -135,6 +132,10 @@ BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_PODMAN=y
BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y
BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_QUERIERD=y
BR2_PACKAGE_LIBINPUT=y
+5 -4
View File
@@ -30,7 +30,6 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/lin
BR2_LINUX_KERNEL_INSTALL_TARGET=y
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y
BR2_PACKAGE_STRACE=y
BR2_PACKAGE_STRESS_NG=y
BR2_PACKAGE_JQ=y
@@ -49,7 +48,7 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y
BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_LIBSSH_OPENSSL=y
BR2_PACKAGE_LIBOPENSSL_BIN=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_NETOPEER2_CLI=y
@@ -66,7 +65,6 @@ BR2_PACKAGE_FPING=y
BR2_PACKAGE_FRR=y
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
@@ -84,7 +82,6 @@ BR2_PACKAGE_SOCAT=y
BR2_PACKAGE_TCPDUMP=y
BR2_PACKAGE_TRACEROUTE=y
BR2_PACKAGE_ULOGD=y
BR2_PACKAGE_BASH=y
BR2_PACKAGE_BASH_COMPLETION=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_HTOP=y
@@ -138,6 +135,10 @@ BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_PODMAN=y
BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y
BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_QUERIERD=y
GNS3_APPLIANCE_RAM=512
+3
View File
@@ -1,2 +1,5 @@
d /run/containers/inbox 0700 - -
d /run/containers/queue 0700 - -
d /run/containers/done 0700 - -
d /run/cni 0755 - -
L+ /var/lib/cni - - - - /run/cni
+1 -1
View File
@@ -219,7 +219,7 @@ sysrepoctl -s $SEARCH \
-i infix-ip@2023-09-14.yang -g wheel -p 0660 \
-i infix-if-type@2023-08-21.yang -g wheel -p 0660 \
-i infix-routing@2024-01-09.yang -g wheel -p 0660 \
-i infix-interfaces@2023-09-19.yang -g wheel -p 0660 \
-i infix-interfaces@2024-01-15.yang -g wheel -p 0660 \
-e vlan-filtering \
-i ieee802-dot1ab-lldp@2022-03-15.yang -g wheel -p 0660 \
-i infix-lldp@2023-08-23.yang -g wheel -p 0660 \
+1
View File
@@ -29,6 +29,7 @@ confd_plugin_la_SOURCES = \
ietf-system.c \
ietf-factory-default.c \
ietf-routing.c \
infix-containers.c \
infix-dhcp.c \
infix-services.c \
infix-factory.c \
+6
View File
@@ -55,6 +55,9 @@ int core_post_hook(sr_session_ctx_t *session, uint32_t sub_id, const char *modul
return SR_ERR_SYS;
}
/* Everything done, including interfaces, launch all container scripts */
infix_containers_launch();
/* skip reload in bootstrap, implicit reload in runlevel change */
if (systemf("runlevel >/dev/null 2>&1"))
return SR_ERR_OK;
@@ -110,6 +113,9 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
if (rc)
goto err;
rc = ietf_system_init(&confd);
if (rc)
goto err;
rc = infix_containers_init(&confd);
if (rc)
goto err;
rc = infix_dhcp_init(&confd);
+4
View File
@@ -131,6 +131,10 @@ int ietf_interfaces_init(struct confd *confd);
/* ietf-system.c */
int ietf_system_init(struct confd *confd);
/* infix-containers.c */
int infix_containers_init(struct confd *confd);
void infix_containers_launch(void);
/* infix-dhcp.c */
int infix_dhcp_init(struct confd *confd);
+262
View File
@@ -26,6 +26,263 @@
#define IF_XPATH "/ietf-interfaces:interfaces/interface"
static bool iface_is_cni(const char *ifname, struct lyd_node *cif)
{
struct lyd_node *cni = lydx_get_child(cif, "container-network");
if (cni)
return true;
return false;
}
static void cni_gen_addrs(struct lyd_node *ip, FILE *fp, int *first)
{
struct lyd_node *addr;
if (!lydx_is_enabled(ip, "enabled"))
return;
LYX_LIST_FOR_EACH(lyd_child(ip), addr, "address") {
struct lyd_node *ip = lydx_get_child(addr, "ip");
struct lyd_node *len = lydx_get_child(addr, "prefix-length");
if (*first)
fprintf(fp, ",\n \"addresses\": [\n");
fprintf(fp, "%s { \"address\": \"%s/%s\" }",
*first ? "" : ",\n", lyd_get_value(ip), lyd_get_value(len));
*first = 0;
}
}
#if 0 /* Unused for now, use container specific global dns and search settings instead. */
static void cni_gen_dns(struct lyd_node *cni, FILE *fp, int *first)
{
struct lyd_node *dns;
dns = lydx_get_child(cni, "dns");
if (dns) {
struct lyd_node *node;
puts("Adding DNS to CNI profile!");
fprintf(fp, ",\n \"dns\": {");
*first = 1;
LYX_LIST_FOR_EACH(lyd_child(dns), node, "nameservers") {
if (*first)
fprintf(fp, "\n \"nameservers\": [ ");
else
fprintf(fp, ", ");
printf("Adding DNS nameserver %s to CNI profile!\n", lyd_get_value(node));
fprintf(fp, "\"%s\"", lyd_get_value(node));
(*first)++;
}
if (*first > 1)
fprintf(fp, " ]");
node = lydx_get_child(dns, "domain");
if (node) {
fprintf(fp, "%s\n \"domain\": \"%s\"",
*first > 1 ? "," : "", lyd_get_value(node));
(*first)++;
}
LYX_LIST_FOR_EACH(lyd_child(cni), node, "search") {
if (*first)
fprintf(fp, "%s\n \"search\": [ ", *first > 1 ? "," : "");
else
fprintf(fp, ", ");
fprintf(fp, "\"%s\"", lyd_get_value(node));
*first = 0;
}
fprintf(fp, "%s\n }", *first ? "" : "]");
}
}
#endif
/*
* Set up IP masquerading bridge which acts as a gateway for nodes behind it.
* Default subnet, if one is missing in configuration, is: 10.88.0.0/16
*/
static int cni_bridge(struct lyd_node *cni, const char *ifname)
{
struct lyd_node *net;
int first = 1;
FILE *fp;
fp = fopenf("w", "/etc/cni/net.d/90-%s-bridge.conflist", ifname);
if (!fp)
return -EIO;
fprintf(fp, "{\n"
" \"cniVersion\": \"1.0.0\",\n"
" \"name\": \"%s\",\n"
" \"plugins\": [\n"
" {\n"
" \"type\": \"bridge\",\n"
" \"bridge\": \"%s\",\n"
" \"isGateway\": true,\n"
" \"ipMasq\": true,\n"
" \"hairpinMode\": true,\n"
// " \"dataDir\": \"/run/containers/networks\",\n"
" \"ipam\": {\n"
" \"type\": \"host-local\"", ifname, ifname);
LYX_LIST_FOR_EACH(lyd_child(cni), net, "route") {
struct lyd_node *subnet = lydx_get_child(net, "subnet");
struct lyd_node *gateway = lydx_get_child(net, "gateway");
if (first)
fprintf(fp, ",\n \"routes\": [\n");
else
fprintf(fp, ",\n");
fprintf(fp, " {\n"
" \"dst\": \"%s\"%s\n", lyd_get_value(subnet), gateway ? "," : "");
if (gateway)
fprintf(fp, " \"gw\": \"%s\"\n", lyd_get_value(gateway));
fprintf(fp, " }");
first = 0;
}
if (!first)
fprintf(fp, " ]");
else
fprintf(fp, ",\n \"routes\": [ { \"dst\": \"0.0.0.0/0\" } ]");
first = 1;
LYX_LIST_FOR_EACH(lyd_child(cni), net, "subnet") {
struct lyd_node *subnet = lydx_get_child(net, "subnet");
struct lyd_node *gateway = lydx_get_child(net, "gateway");
if (first)
fprintf(fp, ",\n \"ranges\": [\n");
else
fprintf(fp, ",\n");
fprintf(fp, " [{\n"
" \"subnet\": \"%s\"%s\n", lyd_get_value(subnet), gateway ? "," : "");
if (gateway)
fprintf(fp, " \"gateway\": \"%s\"\n", lyd_get_value(gateway));
fprintf(fp, " }]");
first = 0;
}
if (!first)
fprintf(fp, " ]");
else
/* Default is a customary docker0 local network */
fprintf(fp, ",\n \"ranges\": [ [{ \"subnet\": \"172.17.0.0/16\" }] ]");
fprintf(fp,
"\n }\n" /* /ipam */
" },\n" /* /bridge */
" {\n"
" \"type\": \"portmap\",\n"
" \"capabilities\": {\n"
" \"portMappings\": true\n"
" }\n"
" },\n" /* /portmap */
" {\n"
" \"type\": \"firewall\"\n"
" },\n" /* /firewall */
" {\n"
" \"type\": \"tuning\"\n"
" }\n" /* /tuning */
" ]\n"
"}\n");
if (fclose(fp))
return -errno;
return 0;
}
static int cni_host(struct lyd_node *cni, const char *ifname)
{
struct lyd_node *net, *ip;
int first = 1;
FILE *fp;
fp = fopenf("w", "/etc/cni/net.d/90-%s-host.conflist", ifname);
if (!fp)
return -EIO;
fprintf(fp, "{\n"
" \"cniVersion\": \"1.0.0\",\n"
" \"name\": \"%s\",\n"
" \"plugins\": [\n"
" {\n"
" \"type\": \"host-device\",\n"
" \"device\": \"%s\",\n"
" \"ipam\": {\n"
" \"type\": \"static\"", ifname, ifname);
ip = lydx_get_child(lyd_parent(cni), "ipv4");
if (ip)
cni_gen_addrs(ip, fp, &first);
ip = lydx_get_child(lyd_parent(cni), "ipv6");
if (ip)
cni_gen_addrs(ip, fp, &first);
if (!first)
fprintf(fp, "\n ]");
first = 1;
LYX_LIST_FOR_EACH(lyd_child(cni), net, "route") {
struct lyd_node *subnet = lydx_get_child(net, "subnet");
struct lyd_node *gateway = lydx_get_child(net, "gateway");
if (first)
fprintf(fp, ",\n \"routes\": [\n");
else
fprintf(fp, ",\n");
fprintf(fp, " {\n"
" \"dst\": \"%s\"%s\n", lyd_get_value(subnet), gateway ? "," : "");
if (gateway)
fprintf(fp, " \"gw\": \"%s\"\n", lyd_get_value(gateway));
fprintf(fp, " }");
first = 0;
}
if (!first)
fprintf(fp, " ]");
fprintf(fp,
"\n }\n"
" }\n"
" ]\n"
"}\n");
if (fclose(fp))
return -errno;
return 0;
}
static int iface_gen_cni(const char *ifname, struct lyd_node *cif)
{
struct lyd_node *cni = lydx_get_child(cif, "container-network");
const char *type = lydx_get_cattr(cni, "type");
ERROR("Got CNI %s iface %s", type, ifname);
if (!strcmp(type, "cni-host"))
return cni_host(cni, ifname);
if (!strcmp(type, "cni-bridge"))
return cni_bridge(cni, ifname);
ERROR("Unknown CNI interface type %s, skipping.", type);
return 0;
}
static bool iface_is_phys(const char *ifname)
{
bool is_phys = false;
@@ -1182,6 +1439,11 @@ static sr_error_t netdag_gen_iface(struct dagger *net,
op = LYDX_OP_CREATE;
}
if (iface_is_cni(ifname, cif)) {
err = iface_gen_cni(ifname, cif);
goto err;
}
ip = dagger_fopen_next(net, "init", ifname, 50, "init.ip");
if (!ip) {
err = -EIO;
+213
View File
@@ -0,0 +1,213 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <pwd.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_module.h>
#include <srx/srx_val.h>
#include "core.h"
#define ARPING_MSEC 1000
#define MODULE "infix-containers"
#define CFG_XPATH "/infix-containers:container"
#define INBOX_QUEUE "/run/containers/inbox"
#define JOB_QUEUE "/run/containers/queue"
#define LOGGER "logger -t container -p local1.notice"
static const struct srx_module_requirement reqs[] = {
{ .dir = YANG_PATH_, .name = MODULE, .rev = "2023-12-14" },
{ NULL }
};
static int job(const char *name, struct lyd_node *cif)
{
const char *image = lydx_get_cattr(cif, "image");
struct lyd_node *net;
FILE *fp;
fp = fopenf("w", "%s/%s.sh", INBOX_QUEUE, name);
if (!fp) {
ERROR("Failed adding job %s.sh to job queue" INBOX_QUEUE, name);
return 1;
}
/* Stop any running container gracefully so it releases its IP addresses. */
fprintf(fp, "#!/bin/sh\n"
"container stop %s\n"
"container ", name);
LYX_LIST_FOR_EACH(lyd_child(cif), net, "dns")
fprintf(fp, "--dns %s ", lyd_get_value(net));
LYX_LIST_FOR_EACH(lyd_child(cif), net, "search")
fprintf(fp, "--dns-search %s ", lyd_get_value(net));
fprintf(fp, "create %s %s", name, image);
LYX_LIST_FOR_EACH(lyd_child(cif), net, "network") {
struct lyd_node *opt;
const char *name;
int first = 1;
name = lydx_get_cattr(net, "name");
fprintf(fp, " %s", name);
LYX_LIST_FOR_EACH(lyd_child(net), opt, "option") {
const char *option = lyd_get_value(opt);
fprintf(fp, "%s%s", first ? ":" : ",", option);
first = 0;
}
}
fprintf(fp, "\n");
fchmod(fileno(fp), 0700);
return fclose(fp);
}
static int add(const char *name, struct lyd_node *cif)
{
FILE *fp;
if (job(name, cif))
return SR_ERR_SYS;
fp = fopenf("w", "/etc/finit.d/available/pod:%s.conf", name);
if (!fp) {
ERROR("Failed creating container %s monitor", name);
return SR_ERR_SYS;
}
fprintf(fp, "service name:pod log:prio:local1.err,tag:container :%s pid:!/run/pod:%s.pid \\\n"
" [2345] <usr/pod:%s> podman start -a %s -- Container %s\n",
name, name, name, name, name);
fclose(fp);
if (systemf("initctl -nbq enable pod:%s", name)) {
ERROR("Failed enabling container %s monitor", name);
return SR_ERR_SYS;
}
return 0;
}
static int del(const char *name)
{
char fn[strlen(JOB_QUEUE) + strlen(name) + 5];
/* Remove any pending download/create job first */
snprintf(fn, sizeof(fn), "%s/%s.sh", JOB_QUEUE, name);
erase(fn);
snprintf(fn, sizeof(fn), "%s/%s.sh", INBOX_QUEUE, name);
erase(fn);
return systemf("container delete %s", name);
}
static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *_confd)
{
struct lyd_node *diff, *cifs, *difs, *cif, *dif;
sr_error_t err = 0;
sr_data_t *cfg;
switch (event) {
case SR_EV_DONE:
break;
case SR_EV_CHANGE:
case SR_EV_ABORT:
default:
return SR_ERR_OK;
}
err = sr_get_data(session, CFG_XPATH "//.", 0, 0, 0, &cfg);
if (err)
goto err_abandon;
err = srx_get_diff(session, &diff);
if (err)
goto err_release_data;
cifs = lydx_get_descendant(cfg->tree, "container", "container", NULL);
difs = lydx_get_descendant(diff, "container", "container", NULL);
/* find the modified one, delete or recreate only that */
LYX_LIST_FOR_EACH(difs, dif, "container") {
const char *name = lydx_get_cattr(dif, "name");
ERROR("Change in container %s", name);
if (lydx_get_op(dif) == LYDX_OP_DELETE) {
ERROR("OP DELETE container %s", name);
del(name);
continue;
}
LYX_LIST_FOR_EACH(cifs, cif, "container") {
const char *nm = lydx_get_cattr(cif, "name");
ERROR("container %s vs %s", name, nm);
if (strcmp(name, nm)) {
ERROR("Skipping container %s", nm);
continue;
}
if (!lydx_is_enabled(cif, "enabled")) {
ERROR("container %s not enabled", nm);
del(name);
} else {
ERROR("container %s enabled", nm);
add(name, cif);
}
break;
}
}
lyd_free_tree(diff);
err_release_data:
sr_release_data(cfg);
err_abandon:
return err;
}
void infix_containers_launch(void)
{
struct dirent *d;
DIR *dir;
dir = opendir(INBOX_QUEUE);
if (!dir) {
ERROR("Cannot open %s to launch scripts.", INBOX_QUEUE);
return;
}
while ((d = readdir(dir))) {
char fn[strlen(INBOX_QUEUE) + strlen(d->d_name) + 2];
snprintf(fn, sizeof(fn), "%s/%s", INBOX_QUEUE, d->d_name);
if (movefile(fn, JOB_QUEUE))
ERRNO("Failed moving %s to job queue %s", fn, JOB_QUEUE);
}
}
int infix_containers_init(struct confd *confd)
{
int rc;
rc = srx_require_modules(confd->conn, reqs);
if (rc)
goto fail;
REGISTER_CHANGE(confd->session, MODULE, CFG_XPATH, 0, change, confd, &confd->sub);
return SR_ERR_OK;
fail:
ERROR("init failed: %s", sr_strerror(rc));
return rc;
}
@@ -0,0 +1,186 @@
/*
* Infix Containers YANG module
*/
module infix-containers {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:infix-containers";
prefix infix-cont;
import ietf-yang-types {
prefix yang;
}
import ietf-interfaces {
prefix if;
}
import ietf-inet-types {
prefix inet;
}
import infix-interfaces {
prefix infix-if;
}
revision 2023-12-14 {
description "Initial revision";
reference "internal";
}
/*
* Typedefs
*/
typedef restart-policy {
type enumeration {
enum no {
description "Do not restart containers on exit.";
value 1;
}
enum always {
description "Restart containers when they exit, regardless of status.";
value 2;
}
enum on-failure {
description "Restart containers when they exit with a non-0 exit code.";
value 3;
}
}
}
/*
* Data Nodes
*/
container container {
list container {
key "name";
leaf name {
description "Name of the Docker container";
type string;
}
leaf enabled {
description "Enable or disable a container configuration.";
type boolean;
default true;
}
leaf image {
description "Docker image used for the container";
mandatory true;
type string;
}
leaf restart {
description "Restart policy to follow when containers exit.";
type restart-policy;
default no;
}
leaf manual {
description "Auto-start or manual start after creation/reboot.";
type boolean;
}
choice network {
description "Select network mode: none, host, or container network interfaces.";
case host {
leaf host {
description "Run as host container, with full access to all network interfaces.";
type boolean;
}
}
case network {
list network {
key name;
leaf name {
description "CNI network to connect to the container.";
type if:interface-ref;
must "/if:interfaces/if:interface[if:name = current()]/infix-if:container-network" {
error-message "Container networks must be interfaces classified as container-network.";
}
}
leaf-list option {
description "Options for CNI bridges.
Example: ip=1.2.3.4 to request a specific IP, both IPv4 and IPv6.
interface_name=foo0 name to set interface name inside container.";
type string;
}
}
leaf-list publish {
description "Publish container port, or a range of ports, to the host.";
type string;
}
leaf-list dns {
description "Set custom DNS servers, or 'none' to use /etc/resolv.conf in image.";
type string;
}
leaf-list search {
description "Set custom DNS search domains, or '.' to not set search domain.";
type string;
}
}
}
container statistics {
description "Statistics of the container";
config false;
leaf status {
description "Status of the Docker container";
type enumeration {
enum running;
enum stopped;
}
}
leaf created {
description "Creation timestamp of the container";
type yang:date-and-time;
}
}
}
}
rpc container-pull {
description "Upgrade (fetch) a container image";
input {
leaf image {
description "Name of the container image to upgrade.";
type string;
}
}
}
rpc container-stop {
description "Stop a container";
input {
leaf name {
description "Name of the container to stop";
type string;
}
}
}
rpc container-start {
description "Start a container";
input {
leaf name {
description "Name of the container to start";
type string;
}
}
}
}
@@ -0,0 +1,107 @@
submodule infix-if-container {
yang-version 1.1;
belongs-to infix-interfaces {
prefix infix-if;
}
import ietf-inet-types {
prefix inet;
}
import ietf-interfaces {
prefix if;
}
import infix-if-type {
prefix infixift;
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "CNI network to interface mapper extension for ietf-interfaces.
This ensures a container interface can never be a bridge port,
or LAG member at the same time.";
revision 2024-01-15 {
description "Initial revision.";
reference "internal";
}
/*
* Identities
*/
identity cni-type {
description "CNI network type";
}
identity cni-bridge {
base cni-type;
description "CNI standard bridge with IP masquerading, portmappping, and firewalling.";
}
identity cni-host {
base cni-type;
description "CNI host-device, e.g., one end of a VETH pair or other host interface.";
}
/*
* Data Nodes
*/
augment "/if:interfaces/if:interface/infix-if:port" {
description "Augments the interface model with CNI networks.";
case container-network {
container container-network {
presence "Container network (CNI) mapping.";
leaf type {
description "Bridge or host interface";
type identityref {
base cni-type;
}
default cni-host;
}
list subnet {
description "Static IP ranges to hand out addresses to containers from.
A cni-bridge will forward DNS, NTP, and SSH by default to
the host interfaces.";
when "../type = 'infix-if:cni-bridge'";
key subnet;
leaf subnet {
type inet:ip-prefix;
description "Subnet to assign addresses from, round-robin assignment.
The default is from a standard Docker setup.";
default "172.17.0.0/16";
}
leaf gateway {
type inet:ip-address;
description "Optional gateway address for the subnet, defaults to .1.
This will be used as the address of a cni-bridge.";
}
}
list route {
description "IPv4 or IPv6 routes to be added to container.
For cni-bridge type interfaces the gateway can be omitted,
the the IP address of the CNI bridge will then be used as
the next-hop address.";
key subnet;
leaf subnet {
type inet:ip-prefix;
description "Destination (subnet) IP address.";
}
leaf gateway {
type inet:ip-address;
description "Optional gateway (next-hop) IP address for the route.";
}
}
}
}
}
}
@@ -12,6 +12,7 @@ module infix-interfaces {
include infix-if-base;
include infix-if-bridge;
include infix-if-container;
include infix-if-veth;
include infix-if-vlan;
@@ -19,6 +20,11 @@ module infix-interfaces {
contact "kernelkit@googlegroups.com";
description "Linux bridge and lag extensions for ietf-interfaces.";
revision 2024-01-15 {
description "Add support for container ports (CNI networks).";
reference "internal";
}
revision 2023-09-19 {
description "Add deviation to allow setting phys-address on links.";
reference "internal";
+101
View File
@@ -113,6 +113,27 @@
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="CONTAINERS">
<COMPL>
<ACTION sym="script">container list</ACTION>
</COMPL>
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="CONTAINERSa">
<COMPL>
<ACTION sym="script">container -a list</ACTION>
</COMPL>
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="IMAGES">
<COMPL>
<ACTION sym="script">container -a list images</ACTION>
</COMPL>
<ACTION sym="STRING"/>
</PTYPE>
<VIEW name="main">
<HOTKEY key="^D" cmd="exit"/>
@@ -162,6 +183,65 @@
<ACTION sym="nav">replace config</ACTION>
</COMMAND>
<COMMAND name="container" help="Manage containers and their images" mode="switch">
<COMMAND name="attach" help="Attach to a running container (exec sh)">
<PARAM name="name" ptype="/CONTAINERS" help="Container name" />
<SWITCH name="optional" min="0">
<COMMAND name="command" help="Command to run">
<PARAM name="command" ptype="/STRING" help="Command to run2"/>
</COMMAND>
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
if [ -z "$KLISH_PARAM_name" ]; then
echo "Missing container name."
else
cmd=${KLISH_PARAM_command:-sh}
container attach $KLISH_PARAM_name $cmd
fi
</ACTION>
</COMMAND>
<COMMAND name="pull" help="Fetch a container image">
<PARAM name="image" ptype="/STRING" help="Image url and tag, e.g. docker://hello-world:latest" />
<SWITCH name="optional" min="0">
<COMMAND name="creds" help="Credentials">
<PARAM name="creds" ptype="/STRING" help="[username[:password]]"/>
</COMMAND>
</SWITCH>
<ACTION sym="script" out="tty" interrupt="true">
creds==${KLISH_PARAM_creds:+--creds=$KLISH_PARAM_creds}
container pull $creds $KLISH_PARAM_image
</ACTION>
</COMMAND>
<COMMAND name="remove" help="Remove a container image from local storage">
<PARAM name="name" ptype="/IMAGES" help="Image name" />
<ACTION sym="script" out="tty" interrupt="true">container remove $KLISH_PARAM_name</ACTION>
</COMMAND>
<COMMAND name="run" help="Run a container image, with optional command">
<PARAM name="image" ptype="/IMAGES" help="Image name" />
<SWITCH name="optional" min="0">
<COMMAND name="command" help="Command to run">
<PARAM name="command" ptype="/STRING" help="Command to run2"/>
</COMMAND>
</SWITCH>
<ACTION sym="script" out="tty" interrupt="true">
container run $KLISH_PARAM_image ${KLISH_PARAM_command}
</ACTION>
</COMMAND>
<COMMAND name="start" help="Start a container">
<PARAM name="name" ptype="/CONTAINERSa" help="Container name" />
<ACTION sym="script" out="tty" interrupt="true">container start $KLISH_PARAM_name</ACTION>
</COMMAND>
<COMMAND name="stop" help="Stop a container">
<PARAM name="name" ptype="/CONTAINERS" help="Container name" />
<ACTION sym="script" out="tty" interrupt="true">container stop $KLISH_PARAM_name</ACTION>
</COMMAND>
</COMMAND>
<COMMAND name="copy" help="Copy configuration, e.g., copy running-config startup-config">
<PARAM name="src" ptype="/DATASTORE" help="Source datastore"/>
<PARAM name="dst" ptype="/RW_DATASTORE" help="Destination datastore"/>
@@ -241,6 +321,27 @@
</SWITCH>
</COMMAND>
<COMMAND name="container" help="Show container status">
<ACTION sym="script">podman ps</ACTION>
<SWITCH name="optional" min="0">
<COMMAND name="images" help="Show container images">
<ACTION sym="script">podman images</ACTION>
<SWITCH name="optional" min="0">
<COMMAND name="all" help="All images (default hides intermediate imaages)">
<ACTION sym="script">podman images -a</ACTION>
</COMMAND>
</SWITCH>
</COMMAND>
<COMMAND name="all" help="Show all containers (default only running)">
<ACTION sym="script">podman ps -a</ACTION>
</COMMAND>
<COMMAND name="log" help="Show container log (alias for show log container)">
<ACTION sym="script">cat /log/container</ACTION>
</COMMAND>
</SWITCH>
</COMMAND>
<COMMAND name="datetime" help="Show current date and time, default RFC2822 format">
<ACTION sym="script">date -R</ACTION>
<SWITCH name="optional" min="0">