1#!/usr/bin/env python3

2

3"""

4 Script to test that the system is NOT running prerelease software

5

6 Copyright (c) 2018 Canonical Ltd.

7

8 Authors

9 Rod Smith <rod.smith@canonical.com>

10

11 This program is free software: you can redistribute it and/or modify

12 it under the terms of the GNU General Public License version 3,

13 as published by the Free Software Foundation.

14

15 This program is distributed in the hope that it will be useful,

16 but WITHOUT ANY WARRANTY; without even the implied warranty of

17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

18 GNU General Public License for more details.

19

20 You should have received a copy of the GNU General Public License

21 along with this program. If not, see <http://www.gnu.org/licenses/>.

22

23 The purpose of this script is to identify whether an EFI-based

24 system booted from the network (test passes) or from a local disk

25(test fails).

26

27 Usage:

28 check-prerelease.py

29"""

30 import logging

31 import os

32 import platform

33 import shlex

34 import sys

35

36 from subprocess import CalledProcessError, check_output

37

38

39 def get_apt_cache_information(command: str):

40 """Execute the given apt-cache command and return the information.

41

42 This function runs the specified apt-cache command using the `check_output`

43 function, which returns the information about the Linux kernel package

44 queried by the command.

45

46 :param command: A string representing the apt-cache command to be executed.

47

48 :return:

49 A string containing the information retrieved from the apt-cache

50 command.

51

52 :raises CalledProcessError:

53 If the apt-cache command returns an empty string with exit code 0,

54 indicating a non-existent package.

55 :raises SystemExit:

56 If the apt-cache command returns an error status, indicating that

57 the kernel does not match any installed package.

58 """

59 try:

60 aptinfo = check_output(shlex.split(command), universal_newlines=True)

61 # "apt-cache showpkg" returns an empty string with exit code 0 if

62 # called on a non-existent package.

63 if not aptinfo:

64 raise CalledProcessError(returncode=1, cmd=command)

65 return aptinfo

66 except CalledProcessError as e:

67 # "apt-cache show" returns an error status if called on a

68 # non-existent package.

69 logging.error(e)

70 logging.error("* Kernel does not match any installed package!")

71 raise SystemExit(1)

72

73

74 def verify_apt_cache_showpkg(kernel_release: str):

75 """Check kernel to see if it's supported for certification

76 by "apt-cache showpkg linux-image-<kernel_release>"

77

78 :returns:

79 True if OK, False if not

80 """

81 command = "apt-cache showpkg linux-image-{}".format(kernel_release)

82 aptinfo = get_apt_cache_information(command)

83 # Exclude kernels that come from obvious PPAs....

84 retval = True

85 if "ppa.launchpad.net" in aptinfo:

86 logging.error("* Kernel appears to have come from a PPA!")

87 retval = False

88

89 # Exclude kernels that don't come from the specific Ubuntu repository

90 target_repo = os.environ.get("KERNEL_REPO", "main")

91 if "{}_binary".format(target_repo) not in aptinfo:

92 logging.error(

93 "* Kernel does not come from the {} Ubuntu repository!".format(

94 target_repo

95 )

96 )

97 retval = False

98 return retval

99

100

101 def verify_apt_cache_show(kernel_release: str):

102 """Check kernel to see if it's supported for certification

103 by "apt-cache show linux-image-<kernel_release>"

104

105 :returns:

106 True if OK, False if not

107 """

108 command = "apt-cache show linux-image-{}".format(kernel_release)

109 aptinfo = get_apt_cache_information(command)

110 retval = True

111

112 # Exclude 'edge' kernels, which are identified via the 'Source:' line

113 # in the apt-cache show output....

114 for source in ["Source: linux-signed-hwe-edge", "Source: linux-hwe-edge"]:

115 if source in aptinfo:

116 logging.error("* Kernel is an 'edge' kernel!, found '{}'")

117 retval = False

118

119 # Exclude kernels that aren't from the "linux" (or variant, like

120 # "linux-hwe" or "linux-signed") source....

121 if "Source: linux" not in aptinfo:

122 logging.error("* Kernel is not a Canonical kernel!")

123 retval = False

124

125 return retval

126

127

128 def verify_not_lowlatency_kernel(kernel_release: str):

129 """Check kernel to see if it's supported for certification

130 by verifying the "lowlatency" term not in kernel string

131

132 :returns:

133 True if OK, False if not

134 """

135 # Exclude low-latency kernels, which are identified via the kernel name

136 # string itself....

137 if "lowlatency" in kernel_release:

138 logging.error("* Kernel is a low-latency kernel!")

139 return False

140 return True

141

142

143 def check_kernel_status():

144 """Check kernel to see if it's supported for certification

145

146 :returns:

147 True if OK, False if not

148 """

149 kernel_release = platform.release()

150 logging.info("* Kernel release is {}".format(kernel_release))

151

152 is_valid_kernel = True

153 is_valid_kernel &= verify_apt_cache_showpkg(kernel_release)

154 is_valid_kernel &= verify_apt_cache_show(kernel_release)

155 is_valid_kernel &= verify_not_lowlatency_kernel(kernel_release)

156

157 if not is_valid_kernel:

158 logging.error("* Kernel is ineligible for certification!")

159

160 return is_valid_kernel

161

162

163 def check_os_status():

164 """Check OS to see if it's supported for certification. The OS must be

165 BOTH an LTS version and a non-development branch to pass this test.

166

167 :returns:

168 True if OK, False if not

169 """

170 retval = True

171 command = "lsb_release -s -d"

172 lsbinfo = check_output(shlex.split(command), universal_newlines=True)

173

174 # OS information include '(development branch)' on pre-release

175 # installations. Such installations should fail this test.

176 if "(development branch)" in lsbinfo:

177 print("* OS is reported as a development branch:")

178 print("* {}".format(lsbinfo))

179 retval = False

180 print("")

181

182 if "LTS" not in lsbinfo:

183 print("* OS is a non-LTS version:")

184 print("* {}".format(lsbinfo))

185 retval = False

186

187 return retval

188

189

190 def main():

191 """Check to see if the machine is running pre-release kernel or OS."""

192

193 retval = 0

194 if not check_kernel_status():

195 retval = 1

196 if not check_os_status():

197 retval += 2

198 if retval == 0:

199 print("** All OK; production kernel and OS.")

200 elif retval == 1:

201 print("** Test FAILS; running ineligible kernel!")

202 elif retval == 2:

203 print("** Test FAILS; running pre-release or non-LTS OS!")

204 else:

205 print("** Test FAILS; running pre-release OS with ineligible kernel!")

206

207 return retval

208

209

210 if __name__ == "__main__":

211 sys.exit(main())