Skip to content
This repository was archived by the owner on Dec 12, 2022. It is now read-only.

Commit 22a6a0e

Browse files
Revamp script for a more Pythonic experience
ubports-qa had a few papercuts. Namely: 1. It wouldn't remount the system when installing or updating 2. It was easy for a developer to forget to mount or unmount (which caused point 1) 3. Failure exits return 0 4. It used the old style of formatting strings which is generally regarded as bad practice 5. Remounting read-only will fail if anything wakes up and grabs a file on the rootfs. Handle that and return an error. 6. It didn't lint. a. Some of the function names clobbered built-in keywords. This fixes that.
1 parent 8fdb197 commit 22a6a0e

File tree

1 file changed

+85
-45
lines changed

1 file changed

+85
-45
lines changed

ubports-qa

Lines changed: 85 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ The UBports QA scripts allow you to efficiently manage PPAs from repo.ubports.co
55
Copyright 2018 UBports Foundation
66
Licensed GPL v. 3 or later
77
"""
8-
9-
import requests, subprocess, os, argparse
8+
import subprocess
9+
import os
10+
import argparse
1011
from enum import Enum
12+
import requests
1113

12-
GITHUB_API_PULLREQUEST="https://api.github.com/repos/ubports/%s/pulls/%s"
13-
JENKINS_API_BUILD="https://ci.ubports.com/blue/rest/organizations/jenkins/pipelines/%s/branches/%s"
14-
REPO="http://repo.ubports.com/dists/%s/main/binary-armhf/Packages"
14+
GITHUB_API_PULLREQUEST="https://api.github.com/repos/ubports/{repo}/pulls/{num}"
15+
JENKINS_API_BUILD="https://ci.ubports.com/blue/rest/organizations/jenkins/pipelines/{repo}/branches/{ref}"
1516

1617
is_root = (os.geteuid() == 0)
1718

@@ -20,28 +21,60 @@ class Status(Enum):
2021
BULDING = 2
2122
FAILED = 3
2223

24+
class WritableRootFS():
25+
"""
26+
A class to be used with the `with` statement to mount `/` read-write, for example::
27+
28+
with WritableRootFS():
29+
write_file(/good_file)
30+
31+
`/` will be remounted read-only on close, unless the file /userdata/.writable_image
32+
exists.
33+
"""
34+
def __enter__(self):
35+
self.attempt_writable_mount()
36+
37+
def __exit__(self, type, value, traceback):
38+
self.attempt_unmount()
39+
40+
@classmethod
41+
def attempt_writable_mount(cls):
42+
"""Tries to mount the rootfs read-write"""
43+
ensure_root()
44+
subprocess.run(["mount", "-o", "rw,remount", "/"])
45+
46+
@classmethod
47+
def attempt_unmount(cls):
48+
if not os.path.exists("/userdata/.writable_image"):
49+
ensure_root()
50+
os.sync()
51+
try:
52+
subprocess.run(["mount", "-o", "ro,remount", "/"], check=True)
53+
except subprocess.CalledProcessError:
54+
print_error("Failed to remount root filesystem read-only.")
55+
print_error("Please consider rebooting your device.")
56+
2357
def ensure_root():
2458
if not is_root:
2559
die("Insufficient permissions, please run with sudo.")
2660

27-
def mount():
28-
subprocess.call(["mount", "-o", "rw,remount", "/"])
29-
30-
def unmount():
31-
if not os.path.exists("/userdata/.writable_image"):
32-
subprocess.call(["mount", "-o", "ro,remount", "/"])
33-
3461
def apt_update():
35-
subprocess.call(["apt", "update"])
62+
try:
63+
subprocess.run(["apt", "update"], check=True)
64+
except subprocess.CalledProcessError:
65+
print_error("Failed to run 'apt update'. See the output above for details.")
3666

3767
def apt_upgrade():
38-
subprocess.call(["apt", "upgrade"])
68+
try:
69+
subprocess.run(["apt", "upgrade"], check=True)
70+
except subprocess.CalledProcessError:
71+
print_error("Failed to run 'apt upgrade'. See the output above for details.")
3972

4073
def get_list_file(branch):
41-
return "/etc/apt/sources.list.d/ubports-%s.list" % branch
74+
return "/etc/apt/sources.list.d/ubports-{}.list".format(branch)
4275

4376
def get_pref_file(branch):
44-
return "/etc/apt/preferences.d/ubports-%s.pref" % branch
77+
return "/etc/apt/preferences.d/ubports-{}.pref".format(branch)
4578

4679
def list_exists(branch):
4780
return os.path.isfile(get_list_file(branch))
@@ -57,7 +90,7 @@ def add_list(branch):
5790
if requests.get("http://repo.ubports.com/dists/" + branch).status_code != 200:
5891
die("PPA not found")
5992
with open(get_list_file(branch),"w+") as list:
60-
list.write("deb http://repo.ubports.com/ %s main" % branch)
93+
list.write("deb http://repo.ubports.com/ {} main".format(branch))
6194

6295
def remove_list(branch):
6396
# If it does not exist, just ignore
@@ -66,13 +99,13 @@ def remove_list(branch):
6699
os.remove(get_list_file(branch))
67100

68101
def get_github_pr(repo, num):
69-
ret = requests.get(GITHUB_API_PULLREQUEST % (repo, num))
102+
ret = requests.get(GITHUB_API_PULLREQUEST.format(repo=repo, num=num))
70103
if ret.status_code != 200:
71104
die("Pull-Request not found")
72105
return ret.json()
73106

74107
def get_jenkins_build(repo, ref):
75-
ret = requests.get(JENKINS_API_BUILD % (repo, ref))
108+
ret = requests.get(JENKINS_API_BUILD.format(repo=repo, ref=ref))
76109
if ret.status_code != 200:
77110
die("Jenkins build not found")
78111
return ret.json()
@@ -90,65 +123,72 @@ def get_issue_status(repo, ref):
90123
def get_issue_branch(repo, num):
91124
return get_github_pr(repo, num)["head"]["ref"]
92125

126+
def print_error(error):
127+
"""Prints red text found in error"""
128+
print('\033[91m' + error + '\033[0m')
129+
93130
def die(m):
94-
print(m)
95-
exit()
131+
"""Prints red text m and exits with status 3"""
132+
print_error(m)
133+
exit(3)
96134

97-
def install(args):
98-
ensure_root()
135+
def install_command(args):
99136
if args.pr != -1:
100137
args.repo.replace("ubports/", "")
101-
ref = get_issue_branch(args.repo, args.pr);
102-
status = get_issue_status(args.repo, ref);
138+
ref = get_issue_branch(args.repo, args.pr)
139+
status = get_issue_status(args.repo, ref)
103140
if status == Status.FAILED:
104141
die("Issue failed to build")
105142
if status == Status.BUILDING:
106143
die("Issue is currently building")
107-
add_list(args.repo)
108-
apt_update()
109-
apt_upgrade()
144+
with WritableRootFS():
145+
add_list(args.repo)
146+
apt_update()
147+
apt_upgrade()
110148

111-
def remove(args):
112-
ensure_root()
149+
def remove_command(args):
113150
if not list_exists(args.repo):
114-
die("Repo %s is not installed" % args.repo)
115-
remove_list(args.repo)
116-
update(args)
151+
die("Repo {} is not installed".format(args.repo))
152+
with WritableRootFS():
153+
remove_list(args.repo)
154+
apt_update()
155+
apt_upgrade()
117156

118-
def list(args):
157+
def list_command(args):
119158
print(" ".join(list_lists()))
120159

121-
def update(args):
122-
ensure_root()
123-
mount()
124-
apt_update()
125-
apt_upgrade()
126-
unmount()
160+
def update_command(args):
161+
with WritableRootFS():
162+
apt_update()
163+
apt_upgrade()
127164

128165
parser = argparse.ArgumentParser(description='The UBports QA scripts allow you to efficiently manage PPAs from repo.ubports.com for testing deb components. See http://docs.ubports.com/en/latest/about/process/ppa.html.')
129166
subparsers = parser.add_subparsers(help='')
130167

131168
parser_install = subparsers.add_parser('install', help='Install a ppa or pull-request', description='Install a ppa or pull-request. See http://docs.ubports.com/en/latest/about/process/ppa.html.')
132169
parser_install.add_argument('repo', type=str, help='Name of a PPA on repo.ubports.com. Alternatively, if the \'pr\' argument is provided, the name of a git repository can be specified to automatically add the PPA from a pull-request.')
133170
parser_install.add_argument('pr', type=int, help='Numeric ID of a pull-request on the git repository specified in the \'repo\' argument. If \'repo\' is supposed to be the name of a ppa, the \'pr\' argument should not be specified.', nargs='?', default=-1)
134-
parser_install.set_defaults(func=install)
171+
parser_install.set_defaults(func=install_command)
135172

136173
parser_remove = subparsers.add_parser('remove', help='Remove and uninstall a PPA', description='Remove and uninstall a ppa')
137174
parser_remove.add_argument('repo', type=str, help='Name of the ppa')
138-
parser_remove.set_defaults(func=remove)
175+
parser_remove.set_defaults(func=remove_command)
139176

140177
parser_list = subparsers.add_parser('list', help='List installed PPAs', description='List installed PPAs')
141-
parser_list.set_defaults(func=list)
178+
parser_list.set_defaults(func=list_command)
142179

143180
parser_update = subparsers.add_parser('update', help='Update all packages using apt', description='Update all packages using apt')
144-
parser_update.set_defaults(func=update)
181+
parser_update.set_defaults(func=update_command)
145182

146183
try:
147184
args = parser.parse_args()
148185
args.func(args)
149186
except IOError as e:
187+
# We weren't allowed to do something with a file.
188+
# Either we aren't root or the disk is read-only.
150189
ensure_root()
151190
die(e)
152191
except AttributeError as e:
192+
# The user typed an incorrect command
153193
parser.print_help()
154-
exit()
194+
exit(4)

0 commit comments

Comments
 (0)