diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index b35ba8d1c..f0db8a6f2 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -1,445 +1,469 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="container" owner="${vyos_conf_scripts_dir}/container.py">
     <properties>
       <help>Container applications</help>
       <priority>450</priority>
     </properties>
     <children>
       <tagNode name="name">
         <properties>
           <help>Container name</help>
           <constraint>
             <regex>[-a-zA-Z0-9]+</regex>
           </constraint>
           <constraintErrorMessage>Container name must be alphanumeric and can contain hyphens</constraintErrorMessage>
         </properties>
         <children>
           <leafNode name="allow-host-networks">
             <properties>
               <help>Allow host networks in container</help>
               <valueless/>
             </properties>
           </leafNode>
           <leafNode name="cap-add">
             <properties>
               <help>Container capabilities/permissions</help>
               <completionHelp>
                 <list>net-admin net-bind-service net-raw setpcap sys-admin sys-module sys-time</list>
               </completionHelp>
               <valueHelp>
                 <format>net-admin</format>
                 <description>Network operations (interface, firewall, routing tables)</description>
               </valueHelp>
               <valueHelp>
                 <format>net-bind-service</format>
                 <description>Bind a socket to privileged ports (port numbers less than 1024)</description>
               </valueHelp>
               <valueHelp>
                 <format>net-raw</format>
                 <description>Permission to create raw network sockets</description>
               </valueHelp>
               <valueHelp>
                 <format>setpcap</format>
                 <description>Capability sets (from bounded or inherited set)</description>
               </valueHelp>
               <valueHelp>
                 <format>sys-admin</format>
                 <description>Administation operations (quotactl, mount, sethostname, setdomainame)</description>
               </valueHelp>
               <valueHelp>
                 <format>sys-module</format>
                 <description>Load, unload and delete kernel modules</description>
               </valueHelp>
               <valueHelp>
                 <format>sys-time</format>
                 <description>Permission to set system clock</description>
               </valueHelp>
               <constraint>
                 <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-module|sys-time)</regex>
               </constraint>
               <multi/>
             </properties>
           </leafNode>
           #include <include/generic-description.xml.i>
           <tagNode name="device">
             <properties>
               <help>Add a host device to the container</help>
             </properties>
             <children>
               <leafNode name="source">
                 <properties>
                   <help>Source device (Example: "/dev/x")</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Source device</description>
                   </valueHelp>
                 </properties>
               </leafNode>
               <leafNode name="destination">
                 <properties>
                   <help>Destination container device (Example: "/dev/x")</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Destination container device</description>
                   </valueHelp>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
           #include <include/generic-disable-node.xml.i>
           <tagNode name="environment">
             <properties>
               <help>Add custom environment variables</help>
               <constraint>
                 <regex>[-_a-zA-Z0-9]+</regex>
               </constraint>
               <constraintErrorMessage>Environment variable name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
             </properties>
             <children>
               <leafNode name="value">
                 <properties>
                   <help>Set environment option value</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Set environment option value</description>
                   </valueHelp>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
           <leafNode name="entrypoint">
             <properties>
               <help>Override the default ENTRYPOINT from the image</help>
               <constraint>
                 <regex>[ !#-%&amp;(-~]+</regex>
               </constraint>
               <constraintErrorMessage>Entrypoint must be ASCII characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
             </properties>
           </leafNode>
           <leafNode name="host-name">
             <properties>
               <help>Container host name</help>
               <constraint>
                 #include <include/constraint/host-name.xml.i>
               </constraint>
               <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>
             </properties>
           </leafNode>
           <leafNode name="image">
             <properties>
               <help>Image name in the hub-registry</help>
             </properties>
           </leafNode>
           <leafNode name="command">
             <properties>
               <help>Override the default CMD from the image</help>
               <constraint>
                 <regex>[ !#-%&amp;(-~]+</regex>
               </constraint>
               <constraintErrorMessage>Command must be ASCII characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
             </properties>
           </leafNode>
           <leafNode name="arguments">
             <properties>
               <help>The command's arguments for this container</help>
               <constraint>
                 <regex>[ !#-%&amp;(-~]+</regex>
               </constraint>
               <constraintErrorMessage>The command's arguments must be ASCII characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
             </properties>
           </leafNode>
           <tagNode name="label">
             <properties>
               <help>Add label variables</help>
               <constraint>
                 <regex>[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?</regex>
               </constraint>
               <constraintErrorMessage>Label variable name must be alphanumeric and can contain hyphen, dots and underscores</constraintErrorMessage>
             </properties>
             <children>
               <leafNode name="value">
                 <properties>
                   <help>Set label option value</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Set label option value</description>
                   </valueHelp>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
           <leafNode name="memory">
             <properties>
               <help>Memory (RAM) available to this container</help>
               <valueHelp>
                 <format>u32:0</format>
                 <description>Unlimited</description>
               </valueHelp>
               <valueHelp>
                 <format>u32:1-16384</format>
                 <description>Container memory in megabytes (MB)</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 0-16384"/>
               </constraint>
               <constraintErrorMessage>Container memory must be in range 0 to 16384 MB</constraintErrorMessage>
             </properties>
             <defaultValue>512</defaultValue>
           </leafNode>
           <leafNode name="shared-memory">
             <properties>
               <help>Shared memory available to this container</help>
               <valueHelp>
                 <format>u32:0</format>
                 <description>Unlimited</description>
               </valueHelp>
               <valueHelp>
                 <format>u32:1-8192</format>
                 <description>Container memory in megabytes (MB)</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 0-8192"/>
               </constraint>
               <constraintErrorMessage>Container memory must be in range 0 to 8192 MB</constraintErrorMessage>
             </properties>
             <defaultValue>64</defaultValue>
           </leafNode>
           <tagNode name="network">
             <properties>
               <help>Attach user defined network to container</help>
               <completionHelp>
                 <path>container network</path>
               </completionHelp>
             </properties>
             <children>
               <leafNode name="address">
                 <properties>
                   <help>Assign static IP address to container</help>
                   <valueHelp>
                     <format>ipv4</format>
                     <description>IPv4 address</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ipv6</format>
                     <description>IPv6 address</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ip-address"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
           <tagNode name="port">
             <properties>
               <help>Publish port to the container</help>
             </properties>
             <children>
               #include <include/listen-address.xml.i>
               <leafNode name="source">
                 <properties>
                   <help>Source host port</help>
                   <valueHelp>
                     <format>u32:1-65535</format>
                     <description>Source host port</description>
                   </valueHelp>
                   <valueHelp>
                     <format>start-end</format>
                     <description>Source host port range (e.g. 10025-10030)</description>
                   </valueHelp>
                   <constraint>
                     <validator name="port-range"/>
                   </constraint>
                 </properties>
               </leafNode>
               <leafNode name="destination">
                 <properties>
                   <help>Destination container port</help>
                   <valueHelp>
                     <format>u32:1-65535</format>
                     <description>Destination container port</description>
                   </valueHelp>
                   <valueHelp>
                     <format>start-end</format>
                     <description>Destination container port range (e.g. 10025-10030)</description>
                   </valueHelp>
                   <constraint>
                     <validator name="port-range"/>
                   </constraint>
                 </properties>
               </leafNode>
               <leafNode name="protocol">
                 <properties>
                   <help>Transport protocol used for port mapping</help>
                   <completionHelp>
                     <list>tcp udp</list>
                   </completionHelp>
                   <valueHelp>
                     <format>tcp</format>
                     <description>Use Transmission Control Protocol for given port</description>
                   </valueHelp>
                   <valueHelp>
                     <format>udp</format>
                     <description>Use User Datagram Protocol for given port</description>
                   </valueHelp>
                   <constraint>
                     <regex>(tcp|udp)</regex>
                   </constraint>
                 </properties>
                 <defaultValue>tcp</defaultValue>
               </leafNode>
             </children>
           </tagNode>
           <leafNode name="restart">
             <properties>
               <help>Restart options for container</help>
               <completionHelp>
                 <list>no on-failure always</list>
               </completionHelp>
               <valueHelp>
                 <format>no</format>
                 <description>Do not restart containers on exit</description>
               </valueHelp>
               <valueHelp>
                 <format>on-failure</format>
                 <description>Restart containers when they exit with a non-zero exit code, retrying indefinitely</description>
               </valueHelp>
               <valueHelp>
                 <format>always</format>
                 <description>Restart containers when they exit, regardless of status, retrying indefinitely</description>
               </valueHelp>
               <constraint>
                 <regex>(no|on-failure|always)</regex>
               </constraint>
             </properties>
             <defaultValue>on-failure</defaultValue>
           </leafNode>
+          <leafNode name="uid">
+            <properties>
+              <help>User ID this container will run as</help>
+              <valueHelp>
+                <format>u32:0-65535</format>
+                <description>User ID this container will run as</description>
+              </valueHelp>
+              <constraint>
+                <validator name="numeric" argument="--range 0-65535"/>
+              </constraint>
+            </properties>
+          </leafNode>
+          <leafNode name="gid">
+            <properties>
+              <help>Group ID this container will run as</help>
+              <valueHelp>
+                <format>u32:0-65535</format>
+                <description>Group ID this container will run as</description>
+              </valueHelp>
+              <constraint>
+                <validator name="numeric" argument="--range 0-65535"/>
+              </constraint>
+            </properties>
+          </leafNode>
           <tagNode name="volume">
             <properties>
               <help>Mount a volume into the container</help>
             </properties>
             <children>
               <leafNode name="source">
                 <properties>
                   <help>Source host directory</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Source host directory</description>
                   </valueHelp>
                 </properties>
               </leafNode>
               <leafNode name="destination">
                 <properties>
                   <help>Destination container directory</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Destination container directory</description>
                   </valueHelp>
                 </properties>
               </leafNode>
               <leafNode name="mode">
                 <properties>
                   <help>Volume access mode ro/rw</help>
                   <completionHelp>
                     <list>ro rw</list>
                   </completionHelp>
                   <valueHelp>
                     <format>ro</format>
                     <description>Volume mounted into the container as read-only</description>
                   </valueHelp>
                   <valueHelp>
                     <format>rw</format>
                     <description>Volume mounted into the container as read-write</description>
                   </valueHelp>
                   <constraint>
                     <regex>(ro|rw)</regex>
                   </constraint>
                 </properties>
                 <defaultValue>rw</defaultValue>
               </leafNode>
               <leafNode name="propagation">
                 <properties>
                   <help>Volume bind propagation</help>
                   <completionHelp>
                     <list>shared slave private rshared rslave rprivate</list>
                   </completionHelp>
                   <valueHelp>
                     <format>shared</format>
                     <description>Sub-mounts of the original mount are exposed to replica mounts</description>
                   </valueHelp>
                   <valueHelp>
                     <format>slave</format>
                     <description>Allow replica mount to see sub-mount from the original mount but not vice versa</description>
                   </valueHelp>
                   <valueHelp>
                     <format>private</format>
                     <description>Sub-mounts within a mount are not visible to replica mounts or the original mount</description>
                   </valueHelp>
                   <valueHelp>
                     <format>rshared</format>
                     <description>Allows sharing of mount points and their nested mount points between both the original and replica mounts</description>
                   </valueHelp>
                   <valueHelp>
                     <format>rslave</format>
                     <description>Allows mount point and their nested mount points between original an replica mounts</description>
                   </valueHelp>
                   <valueHelp>
                     <format>rprivate</format>
                     <description>No mount points within original or replica mounts in any direction</description>
                   </valueHelp>
                   <constraint>
                     <regex>(shared|slave|private|rshared|rslave|rprivate)</regex>
                   </constraint>
                 </properties>
                 <defaultValue>rprivate</defaultValue>
               </leafNode>
             </children>
           </tagNode>
         </children>
       </tagNode>
       <tagNode name="network">
         <properties>
           <help>Network name</help>
           <constraint>
             <regex>[-_a-zA-Z0-9]{1,11}</regex>
           </constraint>
           <constraintErrorMessage>Network name cannot be longer than 11 characters</constraintErrorMessage>
         </properties>
         <children>
           #include <include/generic-description.xml.i>
           <leafNode name="prefix">
             <properties>
               <help>Prefix which allocated to that network</help>
               <valueHelp>
                 <format>ipv4net</format>
                 <description>IPv4 network prefix</description>
               </valueHelp>
               <valueHelp>
                 <format>ipv6net</format>
                 <description>IPv6 network prefix</description>
               </valueHelp>
               <constraint>
                 <validator name="ipv4-prefix"/>
                 <validator name="ipv6-prefix"/>
               </constraint>
               <multi/>
             </properties>
           </leafNode>
           #include <include/interface/vrf.xml.i>
         </children>
       </tagNode>
       <tagNode name="registry">
         <properties>
           <help>Registry Name</help>
         </properties>
         <defaultValue>docker.io quay.io</defaultValue>
         <children>
           #include <include/interface/authentication.xml.i>
           #include <include/generic-disable-node.xml.i>
         </children>
       </tagNode>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index cdf46a6e1..9094e27dd 100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -1,192 +1,214 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2023 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 unittest
 import glob
 import json
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 from ipaddress import ip_interface
 
 from vyos.configsession import ConfigSessionError
 from vyos.utils.process import cmd
 from vyos.utils.process import process_named_running
 from vyos.utils.file import read_file
 
 base_path = ['container']
 cont_image = 'busybox:stable' # busybox is included in vyos-build
 PROCESS_NAME = 'conmon'
 PROCESS_PIDFILE = '/run/vyos-container-{0}.service.pid'
 
 busybox_image_path = '/usr/share/vyos/busybox-stable.tar'
 
 def cmd_to_json(command):
     c = cmd(command + ' --format=json')
     data = json.loads(c)[0]
     return data
 
 class TestContainer(VyOSUnitTestSHIM.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestContainer, cls).setUpClass()
 
         # Load image for smoketest provided in vyos-build
         try:
             cmd(f'cat {busybox_image_path} | sudo podman load')
         except:
             cls.skipTest(cls, reason='busybox image not available')
 
         # ensure we can also run this test on a live system - so lets clean
         # out the current configuration :)
         cls.cli_delete(cls, base_path)
 
     @classmethod
     def tearDownClass(cls):
         super(TestContainer, cls).tearDownClass()
 
         # Cleanup podman image
         cmd(f'sudo podman image rm -f {cont_image}')
 
     def tearDown(self):
         self.cli_delete(base_path)
         self.cli_commit()
 
         # Ensure no container process remains
         self.assertIsNone(process_named_running(PROCESS_NAME))
 
         # Ensure systemd units are removed
         units = glob.glob('/run/systemd/system/vyos-container-*')
         self.assertEqual(units, [])
 
     def test_basic(self):
         cont_name = 'c1'
 
         self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '10.0.2.15/24'])
         self.cli_set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '10.0.2.2'])
         self.cli_set(['system', 'name-server', '1.1.1.1'])
         self.cli_set(['system', 'name-server', '8.8.8.8'])
 
         self.cli_set(base_path + ['name', cont_name, 'image', cont_image])
         self.cli_set(base_path + ['name', cont_name, 'allow-host-networks'])
 
         # commit changes
         self.cli_commit()
 
         pid = 0
         with open(PROCESS_PIDFILE.format(cont_name), 'r') as f:
             pid = int(f.read())
 
         # Check for running process
         self.assertEqual(process_named_running(PROCESS_NAME), pid)
 
     def test_ipv4_network(self):
         prefix = '192.0.2.0/24'
         base_name = 'ipv4'
         net_name = 'NET01'
 
         self.cli_set(base_path + ['network', net_name, 'prefix', prefix])
 
         for ii in range(1, 6):
             name = f'{base_name}-{ii}'
             self.cli_set(base_path + ['name', name, 'image', cont_image])
             self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)])
 
         # verify() - first IP address of a prefix can not be used by a container
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         tmp = f'{base_name}-1'
         self.cli_delete(base_path + ['name', tmp])
         self.cli_commit()
 
         n = cmd_to_json(f'sudo podman network inspect {net_name}')
         self.assertEqual(n['subnets'][0]['subnet'], prefix)
 
         # skipt first container, it was never created
         for ii in range(2, 6):
             name = f'{base_name}-{ii}'
             c = cmd_to_json(f'sudo podman container inspect {name}')
             self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway']  , str(ip_interface(prefix).ip + 1))
             self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'], str(ip_interface(prefix).ip + ii))
 
     def test_ipv6_network(self):
         prefix = '2001:db8::/64'
         base_name = 'ipv6'
         net_name = 'NET02'
 
         self.cli_set(base_path + ['network', net_name, 'prefix', prefix])
 
         for ii in range(1, 6):
             name = f'{base_name}-{ii}'
             self.cli_set(base_path + ['name', name, 'image', cont_image])
             self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)])
 
         # verify() - first IP address of a prefix can not be used by a container
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         tmp = f'{base_name}-1'
         self.cli_delete(base_path + ['name', tmp])
         self.cli_commit()
 
         n = cmd_to_json(f'sudo podman network inspect {net_name}')
         self.assertEqual(n['subnets'][0]['subnet'], prefix)
 
         # skipt first container, it was never created
         for ii in range(2, 6):
             name = f'{base_name}-{ii}'
             c = cmd_to_json(f'sudo podman container inspect {name}')
             self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway']      , str(ip_interface(prefix).ip + 1))
             self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix).ip + ii))
 
     def test_dual_stack_network(self):
         prefix4 = '192.0.2.0/24'
         prefix6 = '2001:db8::/64'
         base_name = 'dual-stack'
         net_name = 'net-4-6'
 
         self.cli_set(base_path + ['network', net_name, 'prefix', prefix4])
         self.cli_set(base_path + ['network', net_name, 'prefix', prefix6])
 
         for ii in range(1, 6):
             name = f'{base_name}-{ii}'
             self.cli_set(base_path + ['name', name, 'image', cont_image])
             self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix4).ip + ii)])
             self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix6).ip + ii)])
 
         # verify() - first IP address of a prefix can not be used by a container
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         tmp = f'{base_name}-1'
         self.cli_delete(base_path + ['name', tmp])
         self.cli_commit()
 
         n = cmd_to_json(f'sudo podman network inspect {net_name}')
         self.assertEqual(n['subnets'][0]['subnet'], prefix4)
         self.assertEqual(n['subnets'][1]['subnet'], prefix6)
 
         # skipt first container, it was never created
         for ii in range(2, 6):
             name = f'{base_name}-{ii}'
             c = cmd_to_json(f'sudo podman container inspect {name}')
             self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway']      , str(ip_interface(prefix6).ip + 1))
             self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix6).ip + ii))
             self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway']          , str(ip_interface(prefix4).ip + 1))
             self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress']        , str(ip_interface(prefix4).ip + ii))
 
+    def test_uid_gid(self):
+        cont_name = 'uid-test'
+        gid = '100'
+        uid = '1001'
+
+        self.cli_set(base_path + ['name', cont_name, 'allow-host-networks'])
+        self.cli_set(base_path + ['name', cont_name, 'image', cont_image])
+        self.cli_set(base_path + ['name', cont_name, 'gid', gid])
+
+        # verify() - GID can only be set if UID is set
+        with self.assertRaises(ConfigSessionError):
+            self.cli_commit()
+        self.cli_set(base_path + ['name', cont_name, 'uid', uid])
+
+        self.cli_commit()
+
+        # verify
+        tmp = cmd(f'sudo podman exec -it {cont_name} id -u')
+        self.assertEqual(tmp, uid)
+        tmp = cmd(f'sudo podman exec -it {cont_name} id -g')
+        self.assertEqual(tmp, gid)
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 59d11c5a3..321d00abf 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -1,496 +1,508 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2023 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
 
 from hashlib import sha256
 from ipaddress import ip_address
 from ipaddress import ip_network
 from json import dumps as json_write
 
 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.utils.file import write_file
 from vyos.utils.process import call
 from vyos.utils.process import cmd
 from vyos.utils.process import run
 from vyos.utils.process import rc_cmd
 from vyos.template import bracketize_ipv6
 from vyos.template import inc_ip
 from vyos.template import is_ipv4
 from vyos.template import is_ipv6
 from vyos.template import render
 from vyos.xml_ref import default_value
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 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):
     if os.path.exists('/tmp/vyos.container.debug'):
         print(command)
     return cmd(command)
 
 def 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=('-', '_'),
                                      no_tag_node_value_mangle=True,
                                      get_first_key=True,
                                      with_recursive_defaults=True)
 
     for name in container.get('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)
 
     # registry is a tagNode with default values - merge the list from
     # default_values['registry'] into the tagNode variables
     if 'registry' not in container:
         container.update({'registry' : {}})
         default_values = default_value(base + ['registry'])
         for registry in default_values:
             tmp = {registry : {}}
             container['registry'] = dict_merge(tmp, container['registry'])
 
     # Delete container network, delete containers
     tmp = node_changed(conf, base + ['network'])
     if tmp: container.update({'network_remove' : tmp})
 
     tmp = node_changed(conf, base + ['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():
             # Container image is a mandatory option
             if 'image' not in container_config:
                 raise ConfigError(f'Container image for "{name}" is mandatory!')
 
             # Check if requested container image exists locally. If it does not
             # exist locally - inform the user. This is required as there is a
             # shared container image storage accross all VyOS images. A user can
             # delete a container image from the system, boot into another version
             # of VyOS and then it would fail to boot. This is to prevent any
             # configuration error when container images are deleted from the
             # global storage. A per image local storage would be a super waste
             # of diskspace as there will be a full copy (up tu several GB/image)
             # on upgrade. This is the "cheapest" and fastest solution in terms
             # of image upgrade and deletion.
             image = container_config['image']
             if run(f'podman image exists {image}') != 0:
                 Warning(f'Image "{image}" used in container "{name}" does not exist '\
                         f'locally. Please use "add container image {image}" to add it '\
                         f'to the system! Container "{name}" will not be started!')
 
             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.get('network', {}):
                     raise ConfigError(f'Container network "{network_name}" does not exist!')
 
                 if 'address' in container_config['network'][network_name]:
                     cnt_ipv4 = 0
                     cnt_ipv6 = 0
                     for address in container_config['network'][network_name]['address']:
                         network = None
                         if is_ipv4(address):
                             try:
                                 network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0]
                                 cnt_ipv4 += 1
                             except:
                                 raise ConfigError(f'Network "{network_name}" does not contain an IPv4 prefix!')
                         elif is_ipv6(address):
                             try:
                                 network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0]
                                 cnt_ipv6 += 1
                             except:
                                 raise ConfigError(f'Network "{network_name}" does not contain an IPv6 prefix!')
 
                         # 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():
                     if 'source' not in dev_config:
                         raise ConfigError(f'Device "{dev}" has no source path configured!')
 
                     if 'destination' not in dev_config:
                         raise ConfigError(f'Device "{dev}" has no destination path configured!')
 
                     source = dev_config['source']
                     if not os.path.exists(source):
                         raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')
 
             if 'environment' in container_config:
                 for var, cfg in container_config['environment'].items():
                     if 'value' not in cfg:
                         raise ConfigError(f'Environment variable {var} has no value assigned!')
 
             if 'label' in container_config:
                 for var, cfg in container_config['label'].items():
                     if 'value' not in cfg:
                         raise ConfigError(f'Label variable {var} has no value assigned!')
 
             if 'volume' in container_config:
                 for volume, volume_config in container_config['volume'].items():
                     if 'source' not in volume_config:
                         raise ConfigError(f'Volume "{volume}" has no source path configured!')
 
                     if 'destination' not in volume_config:
                         raise ConfigError(f'Volume "{volume}" has no destination path configured!')
 
                     source = volume_config['source']
                     if not os.path.exists(source):
                         raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!')
 
             if 'port' in container_config:
                 for tmp in container_config['port']:
                     if not {'source', 'destination'} <= set(container_config['port'][tmp]):
                         raise ConfigError(f'Both "source" and "destination" must be specified for a port mapping!')
 
             # 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!')
 
+            # gid cannot be set without uid
+            if 'gid' in container_config and 'uid' not in container_config:
+                raise ConfigError(f'Cannot set "gid" without "uid" for container')
+
     # Add new network
     if 'network' in container:
         for network, network_config in container['network'].items():
             v4_prefix = 0
             v6_prefix = 0
             # If ipv4-prefix not defined for user-defined network
             if 'prefix' not in network_config:
                 raise ConfigError(f'prefix for network "{network}" 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}"!')
 
             # Verify VRF exists
             verify_vrf(network_config)
 
     # A network attached to a container can not be deleted
     if {'network_remove', 'name'} <= set(container):
         for network in container['network_remove']:
             for c, c_config in container['name'].items():
                 if 'network' in c_config and network in c_config['network']:
                     raise ConfigError(f'Can not remove network "{network}", used by container "{c}"!')
 
     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
 
 def generate_run_arguments(name, container_config):
     image = container_config['image']
     memory = container_config['memory']
     shared_memory = container_config['shared_memory']
     restart = container_config['restart']
 
     # Add capability options. Should be in uppercase
     cap_add = ''
     if 'cap_add' in container_config:
         for c in container_config['cap_add']:
             c = c.upper()
             c = c.replace('-', '_')
             cap_add += f' --cap-add={c}'
 
     # Add a host device to the container /dev/x:/dev/x
     device = ''
     if 'device' in container_config:
         for dev, dev_config in container_config['device'].items():
             source_dev = dev_config['source']
             dest_dev = dev_config['destination']
             device += f' --device={source_dev}:{dest_dev}'
 
     # Check/set environment options "-e foo=bar"
     env_opt = ''
     if 'environment' in container_config:
         for k, v in container_config['environment'].items():
             env_opt += f" --env \"{k}={v['value']}\""
 
     # Check/set label options "--label foo=bar"
     label = ''
     if 'label' in container_config:
         for k, v in container_config['label'].items():
             label += f" --label \"{k}={v['value']}\""
 
     hostname = ''
     if 'host_name' in container_config:
         hostname = container_config['host_name']
         hostname = f'--hostname {hostname}'
 
     # Publish ports
     port = ''
     if 'port' in container_config:
         protocol = ''
         for portmap in container_config['port']:
             protocol = container_config['port'][portmap]['protocol']
             sport = container_config['port'][portmap]['source']
             dport = container_config['port'][portmap]['destination']
             listen_addresses = container_config['port'][portmap].get('listen_address', [])
 
             # If listen_addresses is not empty, include them in the publish command
             if listen_addresses:
                 for listen_address in listen_addresses:
                     port += f' --publish {bracketize_ipv6(listen_address)}:{sport}:{dport}/{protocol}'
             else:
                 # If listen_addresses is empty, just include the standard publish command
                 port += f' --publish {sport}:{dport}/{protocol}'
 
+    # Set uid and gid
+    uid = ''
+    if 'uid' in container_config:
+        uid = container_config['uid']
+        if 'gid' in container_config:
+            uid += ':' + container_config['gid']
+        uid = f'--user {uid}'
+
     # Bind volume
     volume = ''
     if 'volume' in container_config:
         for vol, vol_config in container_config['volume'].items():
             svol = vol_config['source']
             dvol = vol_config['destination']
             mode = vol_config['mode']
             prop = vol_config['propagation']
             volume += f' --volume {svol}:{dvol}:{mode},{prop}'
 
     container_base_cmd = f'--detach --interactive --tty --replace {cap_add} ' \
                          f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
-                         f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label}'
+                         f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid}'
 
     entrypoint = ''
     if 'entrypoint' in container_config:
         # it needs to be json-formatted with single quote on the outside
         entrypoint = json_write(container_config['entrypoint'].split()).replace('"', "&quot;")
         entrypoint = f'--entrypoint &apos;{entrypoint}&apos;'
 
     hostname = ''
     if 'host_name' in container_config:
         hostname = container_config['host_name']
         hostname = f'--hostname {hostname}'
 
     command = ''
     if 'command' in container_config:
         command = container_config['command'].strip()
 
     command_arguments = ''
     if 'arguments' in container_config:
         command_arguments = container_config['arguments'].strip()
 
     if 'allow_host_networks' in container_config:
         return f'{container_base_cmd} --net host {entrypoint} {image} {command} {command_arguments}'.strip()
 
     ip_param = ''
     networks = ",".join(container_config['network'])
     for network in container_config['network']:
         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} --no-healthcheck --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:
         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 = {
                 'name': network,
                 'id' : sha256(f'{network}'.encode()).hexdigest(),
                 'driver': 'bridge',
                 'network_interface': f'pod-{network}',
                 'subnets': [],
                 'ipv6_enabled': False,
                 'internal': False,
                 'dns_enabled': True,
                 'ipam_options': {
                     'driver': 'host-local'
                 }
             }
             for prefix in network_config['prefix']:
                 net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)}
                 tmp['subnets'].append(net)
 
                 if is_ipv6(prefix):
                     tmp['ipv6_enabled'] = True
 
             write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2))
 
     if 'registry' in container:
         cmd = f'podman logout --all'
         rc, out = rc_cmd(cmd)
         if rc != 0:
             raise ConfigError(out)
 
         for registry, registry_config in container['registry'].items():
             if 'disable' in registry_config:
                 continue
             if 'authentication' in registry_config:
                 if {'username', 'password'} <= set(registry_config['authentication']):
                     username = registry_config['authentication']['username']
                     password = registry_config['authentication']['password']
                     cmd = f'podman login --username {username} --password {password} {registry}'
                     rc, out = rc_cmd(cmd)
                     if rc != 0:
                         raise ConfigError(out)
 
     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():
             if 'disable' in container_config:
                 continue
 
             file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
             run_args = generate_run_arguments(name, container_config)
             render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args,},
                    formater=lambda _: _.replace("&quot;", '"').replace("&apos;", "'"))
 
     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']:
             file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
             call(f'systemctl stop vyos-container-{name}.service')
             if os.path.exists(file_path):
                 os.unlink(file_path)
 
     call('systemctl daemon-reload')
 
     # Delete old networks if needed
     if 'network_remove' in container:
         for network in container['network_remove']:
             call(f'podman network rm {network} >/dev/null 2>&1')
 
     # Add container
     disabled_new = False
     if 'name' in container:
         for name, container_config in container['name'].items():
             image = container_config['image']
 
             if run(f'podman image exists {image}') != 0:
                 # container image does not exist locally - user already got
                 # informed by a WARNING in verfiy() - bail out early
                 continue
 
             if 'disable' in container_config:
                 # check if there is a container by that name running
                 tmp = _cmd('podman ps -a --format "{{.Names}}"')
                 if name in tmp:
                     file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
                     call(f'systemctl stop vyos-container-{name}.service')
                     if os.path.exists(file_path):
                         disabled_new = True
                         os.unlink(file_path)
                 continue
 
             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():
             network_name = f'pod-{network}'
             # T5147: Networks are started only as soon as there is a consumer.
             # If only a network is created in the first place, no need to assign
             # it to a VRF as there's no consumer, yet.
             if os.path.exists(f'/sys/class/net/{network_name}'):
                 tmp = Interface(network_name)
                 tmp.add_ipv6_eui64_address('fe80::/64')
                 tmp.set_vrf(network_config.get('vrf', ''))
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)