ubuntu-bugcontrol team mailing list archive
-
ubuntu-bugcontrol team
-
Mailing list archive
-
Message #04698
[Merge] ~jslarraz/ubuntu-qa-tools:uvt-snap into ubuntu-qa-tools:master
Jorge Sancho Larraz has proposed merging ~jslarraz/ubuntu-qa-tools:uvt-snap into ubuntu-qa-tools:master.
Commit message:
Add support for uvt snap
Requested reviews:
Ubuntu Bug Control (ubuntu-bugcontrol)
For more details, see:
https://code.launchpad.net/~jslarraz/ubuntu-qa-tools/+git/ubuntu-qa-tools/+merge/462951
Given the limited set of changes needed to properly support uvt in a snap format I wonder if it will be possible to include those changes in the master branch to make it easier to maintain (in contrast of rebasing onto master for new commits)
--
Your team Ubuntu Bug Control is requested to review the proposed merge of ~jslarraz/ubuntu-qa-tools:uvt-snap into ubuntu-qa-tools:master.
diff --git a/snap/hooks/install b/snap/hooks/install
new file mode 100644
index 0000000..7b412fd
--- /dev/null
+++ b/snap/hooks/install
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# Add libvirt configuration to nsswitch.conf
+cat > /etc/nsswitch.conf << "EOF"
+# /etc/nsswitch.conf
+#
+# Example configuration of GNU Name Service Switch functionality.
+# If you have the `glibc-doc-reference' and `info' packages installed, try:
+# `info libc "Name Service Switch"' for information about this file.
+
+passwd: files systemd sss
+group: files systemd sss
+shadow: files systemd sss
+gshadow: files systemd
+
+hosts: files mdns4_minimal [NOTFOUND=return] libvirt dns myhostname
+networks: files
+
+protocols: db files
+services: db files sss
+ethers: db files
+rpc: db files
+
+netgroup: nis sss
+automount: sss
+EOF
+
+# Make /var/lib/libvirt/dnsmasq to show where expected by libnss_libvirt
+ln -s /var/lib/snapd/hostfs/var/lib/libvirt/dnsmasq/ /var/lib/libvirt/
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
new file mode 100644
index 0000000..10284c2
--- /dev/null
+++ b/snap/snapcraft.yaml
@@ -0,0 +1,104 @@
+name: uncomplicated-vm-tools
+version: '0.1'
+summary: "Uncomplicated VM Tools (uvt)"
+description: |
+ uvt is essentially a wrapper script for virsh and virt-install for both
+ making VM creation repeatable and to help batch commands to multiple VMs.
+ These tools use kvm and libvirt, the preferred virtualization technology
+ in Ubuntu. You can check if kvm virtualization is supported by using the
+ `kvm-ok` command.
+confinement: strict
+grade: stable
+base: core22
+
+layout:
+ # Layouts are required by virt-install to work
+ /usr/lib/x86_64-linux-gnu/girepository-1.0:
+ bind: $SNAP/usr/lib/x86_64-linux-gnu/girepository-1.0
+ /usr/share/misc:
+ bind: $SNAP/usr/share/misc
+ /var/lib/usbutils:
+ bind: $SNAP/var/lib/usbutils
+
+ # Address resolution
+ /etc/nsswitch.conf:
+ bind-file: $SNAP_COMMON/etc/nsswitch.conf
+ /var/lib/libvirt:
+ bind: $SNAP_COMMON/var/lib/libvirt
+
+parts:
+ uvt:
+ plugin: python
+ source: vm-tools
+ stage-packages:
+ - cpu-checker # kvm-ok
+ - genisoimage
+ - xorriso
+ - whois
+ - cpio
+ - gzip
+ - qemu-utils # qemu-img
+ - virtinst # virt-install
+ - virt-viewer # virt-viewer
+ - libvirt-clients # virsh
+ - libnss-libvirt # network address resolution
+ - libyajl2 # network address resolution
+ - acl # setfacl: enable libvirt-qemu user to access required folders
+ - python3-lxml
+ - python3-distro-info
+ - gpg # export public gpg key
+ - openssh-client # ssh connections
+ override-build: |
+ snapcraftctl build
+ cp uvt $SNAPCRAFT_PRIME
+ cp uvt-completion.bash $SNAPCRAFT_PRIME
+
+apps:
+ uvt:
+ command: "bin/python3 $SNAP/uvt"
+ completer: "uvt-completion.bash"
+ plugs:
+ - home
+ - network
+ - libvirt # everything
+ - run-libvirt-libvirt-sock-ro # uvt view
+ - x11 # uvt view
+ - gpg-public-keys # uvt new / uvt repo (to add gpg key to vm)
+ - etc-default-keyboard # uvt new (to get keyboard configuration)
+ - ssh-keys # Don't autoconnect, only compat mode
+ - dot-uvt-dot-conf # Don't autoconnect, only compat mode
+ environment:
+ PYTHONPATH: "/usr/lib/python3/dist-packages:$SNAP/usr/lib/python3/dist-packages:$SNAP/lib/python3.10/site-packages:$SNAP/usr/share/virt-manager"
+
+plugs:
+ etc-default-keyboard:
+ interface: system-files
+ read:
+ - /etc/default/keyboard
+
+ run-libvirt-libvirt-sock-ro:
+ interface: system-files
+ write:
+ - /run/libvirt/libvirt-sock-ro
+
+ hostfs-var-lib-libvirt-dnsmasq:
+ interface: system-files
+ read:
+ - /var/lib/snapd/hostfs/var/lib/libvirt/dnsmasq
+
+
+ # Only for compat mode
+ dot-uvt-dot-conf:
+ interface: personal-files
+ read:
+ - $HOME/.uvt.conf
+
+ dot-cache-virt-manager-virt-install-dot-log:
+ interface: personal-files
+ write:
+ - $HOME/.cache/virt-manager/virt-install.log
+
+ dot-config-virt-viewer-settings:
+ interface: personal-files
+ read:
+ - $HOME/.config/virt-viewer/settings
\ No newline at end of file
diff --git a/vm-tools/uvt b/vm-tools/uvt
index 0702939..068df2f 100755
--- a/vm-tools/uvt
+++ b/vm-tools/uvt
@@ -468,6 +468,58 @@ def cmd_cmd():
print("Error: VM '%s' command failed. Aborting." % machine, file=sys.stderr)
sys.exit(1)
+def cmd_ssh():
+ '''Run a command inside a virtual machine'''
+
+ usage = "usage: %prog ssh [options] <vm>"
+
+ epilog = "\n" + \
+ "Eg:\n" + \
+ "$ uvt ssh sec-jammy-amd64\n\n" + \
+ "This will open an interactive session on the single VM named 'sec-jammy-amd64'\n"
+
+ optparse.OptionParser.format_epilog = lambda self, formatter: self.epilog
+ parser = optparse.OptionParser(usage = usage, epilog = epilog)
+
+ parser.add_option("-s", "--start", dest="start", default=False, action='store_true',
+ help="Start the VM (and shutdown if it wasn't running")
+
+ parser.add_option("-t", "--timeout", dest="timeout", default=90, metavar="TIMEOUT",
+ help="wait TIMEOUT seconds for VM to come up if -s is used (default: %default)")
+
+ parser.add_option("-f", "--force-ssh", dest="force_ssh", default=False, action='store_true',
+ help="force the SSH keys to be taken")
+
+ parser.add_option("-r", "--root", dest="root", default=False, action='store_true',
+ help="login to the VM as root")
+
+ parser.add_option("-u", "--user", dest="user", default=None, metavar="USER",
+ help="login to the VM as user")
+
+ parser.add_option("-q", "--quiet", dest="quiet", default=False, action='store_true',
+ help="only report hostnames and output")
+
+ (opt, args) = parser.parse_args()
+ machine = args[0]
+
+ if opt.user is not None and opt.root:
+ print("Error: may specify only one of --root and --user.\n", file=sys.stderr)
+ sys.exit(1)
+
+ print("----- %s -----" % machine)
+ if check_vm_exists(machine) == False:
+ print("Error: VM '%s' does not exist, skipping." % machine, file=sys.stderr)
+ return
+
+ result = vm_run_command(machine, "bash", root=opt.root, start=opt.start,
+ start_timeout=opt.timeout, force_keys=opt.force_ssh,
+ quiet=opt.quiet, output=True, interactive=True,
+ verbose=False, user=opt.user)
+
+ if result == False:
+ print("Error: VM '%s' command failed. Aborting." % machine, file=sys.stderr)
+ sys.exit(1)
+
def cmd_repo():
'''Adds or removes a local repo to a VM'''
@@ -1454,6 +1506,9 @@ def vm_run_command(vm_name, command, root=False, start=False,
ssh_command += ['-q']
ssh_command += ['-o', 'BatchMode=yes']
ssh_command += ['-i', uvt_conf['vm_ssh_key'].split(".pub")[0]]
+ if is_snap():
+ ssh_command += ['-o', 'StrictHostKeyChecking=accept-new']
+ ssh_command += ['-o', 'UserKnownHostsFile=' + os.path.expanduser("~/.ssh/known_hosts")]
ssh_command += [dns_name, command]
@@ -1479,7 +1534,10 @@ def vm_run_command(vm_name, command, root=False, start=False,
def remove_ssh_keys(vm_name):
'''Removes SSH keys for a host'''
- runcmd(["ssh-keygen", "-R", vm_name])
+ cmd = ["ssh-keygen", "-R", vm_name]
+ if is_snap():
+ cmd += ["-f", os.path.expanduser("~/.ssh/known_hosts")]
+ rc, out = runcmd(cmd)
def crypt_password(password):
'''Crypts a password using mkpasswd'''
@@ -1500,7 +1558,6 @@ def vm_start(vm_name):
rc, out = runcmd(["virsh", "--connect", uvt_conf["vm_connect"],
"start", vm_name])
-
def vm_destroy(vm_name):
'''Powers off a VM'''
rc, out = runcmd(["virsh", "--connect", uvt_conf["vm_connect"],
@@ -1578,7 +1635,9 @@ def vm_start_wait(vm_name, timeout=1800, quiet=False, clone_name=None):
def vm_ping(vm_name):
'''Attempts to ping a VM'''
- rc, out = runcmd(["ping", "-c1", "-w1", vm_name])
+ rc = 0
+ if not is_snap():
+ rc, out = runcmd(["ping", "-c1", "-w1", vm_name])
if rc == 0 and ssh_connect(vm_name) == True:
return vm_name
return ""
@@ -3093,6 +3152,9 @@ def runcmd(command, input = None, stderr = subprocess.STDOUT,
'''Try to execute given command (array) and return its stdout, or return
a textual error if it failed.'''
+ if is_snap():
+ stderr = subprocess.DEVNULL
+
try:
sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, shell=shell)
except OSError as e:
@@ -3127,9 +3189,18 @@ def get_gpg_public_key(keyid=None):
return None
print("Exporting GPG key '%s'" % keyid)
- rc, out = runcmd(['gpg', '--export', '--armor', keyid])
+ cmd = ['gpg', '--export', '--armor']
+ if is_snap():
+ cmd += ['--homedir', os.getenv('SNAP_REAL_HOME', os.path.expanduser("~")) + '/.gnupg']
+ rc, out = runcmd(cmd + [keyid])
# make sure something actually got exported
- if rc != 0 or not out.startswith('-----BEGIN PGP PUBLIC KEY BLOCK-----'):
+ # FIXME: without the ability to create lock files, gpg --export returns an error code even if it also return the public key,
+ # splitting this check is a workaround that should be remove after https://github.com/snapcore/snapd/pull/13540 being released
+ # with next snapd version (2.62)
+ if rc != 0 and not is_snap():
+ print("Error: Failed to export public key '%s'." % keyid, file=sys.stderr)
+ return None
+ if not out.startswith('-----BEGIN PGP PUBLIC KEY BLOCK-----'):
print("Error: Failed to export public key '%s'." % keyid, file=sys.stderr)
return None
@@ -3341,9 +3412,11 @@ def check_required_tools():
'kvm-ok' : 'cpu-checker',
'gzip' : 'gzip',
'cpio' : 'cpio',
- 'kvm' : 'qemu-kvm',
'mkpasswd' : 'whois' }
+ if not is_snap():
+ tools['kvm'] = 'qemu-kvm'
+
missing_tools = []
for tool in tools:
ret, out = runcmd(['which', tool])
@@ -3487,15 +3560,15 @@ def load_uvt_config():
if not 'vm_xkboptions' in config:
config['vm_xkboptions'] = keyboard.get('XKBOPTIONS', "")
- # Set a default image size to 8GB and memory to 1024MB
+ # Set a default image size to and memory to default values
if config.get('vm_image_size', "") == "":
- config['vm_image_size'] = "8"
+ config['vm_image_size'] = "8" if not is_snap() else "20"
if config.get('vm_memory', "") == "":
- config['vm_memory'] = "1024"
+ config['vm_memory'] = "1024" if not is_snap() else "4096"
- # Set default vcpus to 1
+ # Set default vcpus
if config.get('vm_vcpus', "") == "":
- config['vm_vcpus'] = "1"
+ config['vm_vcpus'] = "1" if not is_snap() else "4"
# Set a default username and password
if config.get('vm_username', "") == "":
@@ -3615,6 +3688,7 @@ repo Adds or removes a local repo to a VM
update Runs a dist-upgrade inside a VM
clone Clones a VM into a new one
cmd Run a command inside a virtual machine
+ssh Opens an interactive session with the VM
list List virtual machines
view Connect to a virtual machine with VNC
config Create an optional config file (~/%s)
@@ -3647,10 +3721,21 @@ class BetterUbuntuDistroInfo(distro_info.UbuntuDistroInfo):
return release.split()[0] # handle '16.04 LTS' vs '17.10'
+def is_snap():
+ if os.getenv("SNAP") is not None:
+ return True
+ return False
+
#
# Main program
#
+# Update home if needed
+if (os.getenv("UVT_SNAP_COMPAT_MODE") is not None) and (os.getenv("SNAP_REAL_HOME") is not None):
+ os.environ["HOME"] = os.getenv("SNAP_REAL_HOME")
+elif os.getenv("SNAP_USER_COMMON") is not None:
+ os.environ["HOME"] = os.getenv("SNAP_USER_COMMON")
+
config_file = ".uvt.conf"
cmd = None
@@ -3683,6 +3768,7 @@ commands = {
'update' : cmd_update,
'clone' : cmd_clone,
'cmd' : cmd_cmd,
+ 'ssh' : cmd_ssh,
'list' : cmd_list,
'config' : cmd_config,
'dump' : cmd_dump,
Follow ups