Skip to content

Commit 767c9dd

Browse files
committed
Add free-form text to qube for notes, comments, ...
qubesadmin part of adding free-form text to each qube for comments, notes, descriptions, remarks, reminders, etc. fixes: QubesOS/qubes-issues#899 Additional options to skip legacy backup tests or super slow tests
1 parent 02a9b4f commit 767c9dd

File tree

12 files changed

+372
-2
lines changed

12 files changed

+372
-2
lines changed

.gitlab-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ checks:tests:
3434
- docker
3535
variables:
3636
ENABLE_SLOW_TESTS: 1
37+
ENABLE_SUPER_SLOW_TESTS: 1
38+
ENABLE_LEGACY_TESTS: 1
3739
USER: gitlab-runner

doc/conf.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
#
8585
# This is also used if you do content translation via gettext catalogs.
8686
# Usually you set "language" from the command line for these cases.
87-
language = None
87+
language = 'en'
8888

8989
# There are two options for replacing |today|: either, you set today to some
9090
# non-false value, then it is used:
@@ -344,6 +344,8 @@
344344
u'Kill the specified qube', _man_pages_author, 1),
345345
('manpages/qvm-ls', 'qvm-ls',
346346
u'List VMs and various information about them', _man_pages_author, 1),
347+
('manpages/qvm-notes', 'qvm-notes',
348+
u'Manipulate qube notes', _man_pages_author, 1),
347349
('manpages/qvm-pause', 'qvm-pause',
348350
u'Pause a specified qube(s)', _man_pages_author, 1),
349351
('manpages/qvm-pool', 'qvm-pool',
@@ -370,7 +372,6 @@
370372
u'Pause a qube', _man_pages_author, 1),
371373
('manpages/qvm-volume', 'qvm-volume',
372374
u'Manage storage volumes of a qube', _man_pages_author, 1),
373-
374375
('manpages/qubes-prefs', 'qubes-prefs',
375376
u'Display system-wide Qubes settings', _man_pages_author, 1),
376377
]

doc/manpages/qvm-notes.rst

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
.. program:: qvm-notes
2+
3+
:program:`qvm-notes` -- Manipulate qube notes
4+
=============================================
5+
6+
Synopsis
7+
--------
8+
9+
:command:`qvm_notes` [options] *VMNAME* [--edit | --print | --import *FILENAME* | --set '*NOTES*' | --append '*NOTES*' | --delete]
10+
11+
Description
12+
-----------
13+
14+
This command is used to manipulate individual qube notes. Each qube notes is
15+
limited to 256KB of clear text which could contain most UTF-8 characters.
16+
However, some UTF-8 characters will be replaced with underline (`_`) due to
17+
security limitations. Qube notes will be included in backup/restore.
18+
19+
If this command is run outside dom0, it will require `admin.vm.notes.Get` and/or
20+
`admin.vm.notes.Set` access privileges for the target qube in the RPC policies.
21+
22+
General options
23+
---------------
24+
25+
.. option:: --verbose, -v
26+
27+
increase verbosity
28+
29+
.. option:: --quiet, -q
30+
31+
decrease verbosity
32+
33+
.. option:: --help, -h
34+
35+
show this help message and exit
36+
37+
.. option:: --version
38+
39+
show program's version number and exit
40+
41+
.. option:: --force, -f
42+
43+
Do not prompt for confirmation; assume `yes`
44+
45+
Action options
46+
--------------
47+
48+
.. option:: --edit, -e
49+
50+
Edit qube notes in $EDITOR (default text editor). This is the default action.
51+
52+
.. option:: --print, -p
53+
54+
Print qube notes
55+
56+
.. option:: --import=FILENAME, -i FILENAME
57+
58+
Import qube notes from file
59+
60+
.. option:: --set='NOTES', -s 'NOTES'
61+
62+
Set qube notes from the provided string
63+
64+
.. option:: --append='NOTES'
65+
66+
Append the provided string to qube notes
67+
68+
.. option:: --delete, -d
69+
70+
Delete qube notes
71+
72+
Authors
73+
-------
74+
75+
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
76+
| Ali Mirjamali <ali at mirjamali dot com>
77+
78+
| For complete author list see: https://github.com/QubesOS/qubes-core-admin-client.git
79+
80+
.. vim: ts=3 sw=3 et tw=80

qubesadmin/app.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ def clone_vm(self, src_vm, new_name, new_cls=None, *, pool=None, pools=None,
365365
ignore_errors=False, ignore_volumes=None,
366366
ignore_devices=False):
367367
# pylint: disable=too-many-statements
368+
# pylint: disable=too-many-branches
368369
"""Clone Virtual Machine
369370
370371
Example usage with custom storage pools:
@@ -474,6 +475,16 @@ def clone_vm(self, src_vm, new_name, new_cls=None, *, pool=None, pools=None,
474475
if not ignore_errors:
475476
raise
476477

478+
try:
479+
vm_notes = src_vm.get_notes()
480+
if vm_notes:
481+
dst_vm.set_notes(vm_notes)
482+
except qubesadmin.exc.QubesException as e:
483+
dst_vm.log.error(
484+
'Failed to clone qube notes: {!s}'.format(e))
485+
if not ignore_errors:
486+
raise
487+
477488
try:
478489
dst_vm.firewall.save_rules(src_vm.firewall.rules)
479490
except qubesadmin.exc.QubesException as e:

qubesadmin/backup/core3.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ def handle_firewall_xml(self, vm, stream):
5252
except: # pylint: disable=bare-except
5353
vm.log.exception('Failed to set firewall')
5454

55+
def handle_notes_txt(self, vm, stream):
56+
'''Load new (Qubes >= 4.2) notes'''
57+
try:
58+
vm.set_notes(stream.read().decode())
59+
except: # pylint: disable=bare-except
60+
vm.log.exception('Failed to set notes')
61+
5562
class Core3Qubes(qubesadmin.backup.BackupApp):
5663
'''Parsed qubes.xml'''
5764
def __init__(self, store=None):

qubesadmin/backup/restore.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,8 @@ def restore_do(self, restore_info):
19631963
handlers[img_path] = data_func
19641964
handlers[os.path.join(vm_info.subdir, 'firewall.xml')] = \
19651965
functools.partial(vm_info.vm.handle_firewall_xml, vm)
1966+
handlers[os.path.join(vm_info.subdir, 'notes.txt')] = \
1967+
functools.partial(vm_info.vm.handle_notes_txt, vm)
19661968
handlers[os.path.join(vm_info.subdir,
19671969
'whitelisted-appmenus.list')] = \
19681970
functools.partial(self._handle_appmenus_list, vm)

qubesadmin/exc.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,5 +232,9 @@ def __init__(self, prop):
232232
super().__init__("Failed to access '%s' property" % prop)
233233

234234

235+
236+
class QubesNotesError(QubesException):
237+
"""Some problem with qube notes."""
238+
235239
# legacy name
236240
QubesDaemonNoResponseError = QubesDaemonAccessError

qubesadmin/tests/app.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,11 @@ def clone_setup_common_calls(self, src, dst):
358358
(dst, 'admin.vm.firewall.Set', None, rules)] = \
359359
b'0\x00'
360360

361+
# notes
362+
self.app.expected_calls[
363+
(src, 'admin.vm.notes.Get', None, None)] = \
364+
b'0\0'
365+
361366
# storage
362367
for vm in (src, dst):
363368
self.app.expected_calls[

qubesadmin/tests/backup/backupcompatibility.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,14 @@ def create_v4_files(self):
10501050
"tests/backup/v4-firewall.xml"
10511051
f_firewall.write(xml_path.read_bytes())
10521052

1053+
# setup notes only on one VM
1054+
with open(
1055+
self.fullpath("appvms/test-work/notes.txt"),
1056+
"w+",
1057+
encoding="utf-8",
1058+
) as notes:
1059+
notes.write("For Your Eyes Only")
1060+
10531061
# StandaloneVMs
10541062
for vm in ('test-standalonevm', 'test-hvm'):
10551063
os.mkdir(self.fullpath('appvms/{}'.format(vm)))
@@ -1528,6 +1536,8 @@ def create_limited_tmpdir(self, size):
15281536
self.addCleanup(self.cleanup_tmpdir, tmpdir)
15291537
return tmpdir.name
15301538

1539+
@unittest.skipUnless(os.environ.get('ENABLE_LEGACY_TESTS', False),
1540+
'Set ENABLE_LEGACY_TESTS=1 environment variable')
15311541
def test_210_r2(self):
15321542
self.create_v3_backup(False)
15331543
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
@@ -1601,6 +1611,8 @@ def test_210_r2(self):
16011611

16021612
self.assertDom0Restored(dummy_timestamp)
16031613

1614+
@unittest.skipUnless(os.environ.get('ENABLE_LEGACY_TESTS', False),
1615+
'Set ENABLE_LEGACY_TESTS=1 environment variable')
16041616
def test_220_r2_encrypted(self):
16051617
self.create_v3_backup(True)
16061618

@@ -1676,6 +1688,8 @@ def test_220_r2_encrypted(self):
16761688

16771689
self.assertDom0Restored(dummy_timestamp)
16781690

1691+
@unittest.skipUnless(os.environ.get('ENABLE_LEGACY_TESTS', False),
1692+
'Set ENABLE_LEGACY_TESTS=1 environment variable')
16791693
def test_230_r2_uncompressed(self):
16801694
self.create_v3_backup(False, False)
16811695
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
@@ -1781,6 +1795,9 @@ def test_230_r4(self):
17811795
self.app.expected_calls[
17821796
('test-work', 'admin.vm.firewall.Set', None,
17831797
firewall_data.encode())] = b'0\0'
1798+
self.app.expected_calls[
1799+
('test-work', 'admin.vm.notes.Set', None,
1800+
b'For Your Eyes Only')] = b'0\0'
17841801

17851802
qubesd_calls_queue = multiprocessing.Queue()
17861803

@@ -1858,6 +1875,9 @@ def test_230_r4_compressed(self):
18581875
self.app.expected_calls[
18591876
('test-work', 'admin.vm.firewall.Set', None,
18601877
firewall_data.encode())] = b'0\0'
1878+
self.app.expected_calls[
1879+
('test-work', 'admin.vm.notes.Set', None,
1880+
b'For Your Eyes Only')] = b'0\0'
18611881

18621882
qubesd_calls_queue = multiprocessing.Queue()
18631883

@@ -1935,6 +1955,9 @@ def test_230_r4_custom_cmpression(self):
19351955
self.app.expected_calls[
19361956
('test-work', 'admin.vm.firewall.Set', None,
19371957
firewall_data.encode())] = b'0\0'
1958+
self.app.expected_calls[
1959+
('test-work', 'admin.vm.notes.Set', None,
1960+
b'For Your Eyes Only')] = b'0\0'
19381961

19391962
qubesd_calls_queue = multiprocessing.Queue()
19401963

@@ -2049,6 +2072,9 @@ def test_230_r4_uncommon_compression_forced(self):
20492072
self.app.expected_calls[
20502073
('test-work', 'admin.vm.firewall.Set', None,
20512074
firewall_data.encode())] = b'0\0'
2075+
self.app.expected_calls[
2076+
('test-work', 'admin.vm.notes.Set', None,
2077+
b'For Your Eyes Only')] = b'0\0'
20522078

20532079
qubesd_calls_queue = multiprocessing.Queue()
20542080

@@ -2126,6 +2152,9 @@ def test_230_r4_optional_compression(self):
21262152
self.app.expected_calls[
21272153
('test-work', 'admin.vm.firewall.Set', None,
21282154
firewall_data.encode())] = b'0\0'
2155+
self.app.expected_calls[
2156+
('test-work', 'admin.vm.notes.Set', None,
2157+
b'For Your Eyes Only')] = b'0\0'
21292158

21302159
qubesd_calls_queue = multiprocessing.Queue()
21312160

@@ -2171,6 +2200,8 @@ def test_230_r4_optional_compression(self):
21712200

21722201
self.assertDom0Restored(dummy_timestamp)
21732202

2203+
@unittest.skipUnless(os.environ.get('ENABLE_SUPER_SLOW_TESTS', False),
2204+
'Set ENABLE_SUPER_SLOW_TESTS=1 environment variable')
21742205
@unittest.skipUnless(shutil.which('scrypt'),
21752206
"scrypt not installed")
21762207
def test_300_r4_no_space(self):
@@ -2202,6 +2233,9 @@ def test_300_r4_no_space(self):
22022233
self.app.expected_calls[
22032234
('test-work', 'admin.vm.firewall.Set', None,
22042235
firewall_data.encode())] = b'0\0'
2236+
self.app.expected_calls[
2237+
('test-work', 'admin.vm.notes.Set', None,
2238+
b'For Your Eyes Only')] = b'0\0'
22052239

22062240
qubesd_calls_queue = multiprocessing.Queue()
22072241

qubesadmin/tests/tools/qvm_create.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ def test_011_standalonevm(self, check_output_mock):
268268
self.app.expected_calls[
269269
('template', 'admin.vm.volume.CloneFrom', 'root', None)] = \
270270
b'0\0clone-cookie'
271+
self.app.expected_calls[
272+
('template', 'admin.vm.notes.Get', None, None)] = \
273+
b'0\0'
271274
self.app.expected_calls[
272275
('new-vm', 'admin.vm.volume.CloneTo', 'root', b'clone-cookie')] = \
273276
b'0\0'

0 commit comments

Comments
 (0)