Merge pull request #3084 from certbot/sysd_os

Fix merge conflicts in #2818 (Sysd os)
This commit is contained in:
Peter Eckersley 2016-05-27 19:05:25 -07:00
commit 6a97f9edd1
7 changed files with 166 additions and 28 deletions

View file

@ -78,6 +78,8 @@ CLI_DEFAULTS = {
"centos linux": CLI_DEFAULTS_CENTOS,
"fedora": CLI_DEFAULTS_CENTOS,
"red hat enterprise linux server": CLI_DEFAULTS_CENTOS,
"rhel": CLI_DEFAULTS_CENTOS,
"amazon": CLI_DEFAULTS_CENTOS,
"gentoo base system": CLI_DEFAULTS_GENTOO,
"darwin": CLI_DEFAULTS_DARWIN,
}

View file

@ -53,7 +53,7 @@ def _determine_user_agent(config):
if config.user_agent is None:
ua = "CertbotACMEClient/{0} ({1}) Authenticator/{2} Installer/{3}"
ua = ua.format(certbot.__version__, " ".join(util.get_os_info()),
ua = ua.format(certbot.__version__, util.get_os_info_ua(),
config.authenticator, config.installer)
else:
ua = config.user_agent

View file

@ -171,13 +171,13 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
with mock.patch('certbot.main.client.acme_client.ClientNetwork') as acme_net:
self._call_no_clientmock(args)
os_ver = " ".join(util.get_os_info())
os_ver = util.get_os_info_ua()
ua = acme_net.call_args[1]["user_agent"]
self.assertTrue(os_ver in ua)
import platform
plat = platform.platform()
if "linux" in plat.lower():
self.assertTrue(platform.linux_distribution()[0] in ua)
self.assertTrue(util.get_os_info_ua() in ua)
with mock.patch('certbot.main.client.acme_client.ClientNetwork') as acme_net:
ua = "bandersnatch"

7
certbot/tests/testdata/os-release vendored Normal file
View file

@ -0,0 +1,7 @@
NAME="SystemdOS"
VERSION="42.42.42 LTS, Unreal"
ID=systemdos
ID_LIKE=debian
VERSION_ID="42"
HOME_URL="http://www.example.com/"
SUPPORT_URL="http://help.example.com/"

View file

@ -10,8 +10,8 @@ import unittest
import mock
import six
import certbot
from certbot import errors
from certbot.tests import test_util
class RunScriptTest(unittest.TestCase):
@ -340,31 +340,66 @@ class EnforceDomainSanityTest(unittest.TestCase):
u"eichh\u00f6rnchen.example.com")
class GetStrictVersionTest(unittest.TestCase):
"""Tests for certbot.util.get_strict_version."""
class OsInfoTest(unittest.TestCase):
"""Test OS / distribution detection"""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.util import get_strict_version
return get_strict_version(*args, **kwargs)
def test_systemd_os_release(self):
from certbot.util import (get_os_info, get_systemd_os_info,
get_os_info_ua)
def test_two_dev_versions(self):
self.assertTrue(
self._call("0.0.0.dev20151006") < self._call("0.0.0.dev20151008"))
with mock.patch('os.path.isfile', return_value=True):
self.assertEqual(get_os_info(
test_util.vector_path("os-release"))[0], 'systemdos')
self.assertEqual(get_os_info(
test_util.vector_path("os-release"))[1], '42')
self.assertEqual(get_systemd_os_info("/dev/null"), ("", ""))
self.assertEqual(get_os_info_ua(
test_util.vector_path("os-release")),
"SystemdOS")
with mock.patch('os.path.isfile', return_value=False):
self.assertEqual(get_systemd_os_info(), ("", ""))
def test_one_dev_one_release_version(self):
self.assertTrue(self._call("1.0.0.dev0") < self._call("1.0.0"))
self.assertTrue(self._call("1.0.0") < self._call("1.0.1.dev0"))
@mock.patch("certbot.util.subprocess.Popen")
def test_non_systemd_os_info(self, popen_mock):
from certbot.util import (get_os_info, get_python_os_info,
get_os_info_ua)
with mock.patch('os.path.isfile', return_value=False):
with mock.patch('platform.system_alias',
return_value=('NonSystemD', '42', '42')):
self.assertEqual(get_os_info()[0], 'nonsystemd')
self.assertEqual(get_os_info_ua(),
" ".join(get_python_os_info()))
def test_two_release_versions(self):
self.assertTrue(self._call("0.0.0") < self._call("0.0.1"))
self.assertTrue(self._call("0.0.0") < self._call("0.1.0"))
self.assertTrue(self._call("0.0.0") < self._call("1.0.0"))
with mock.patch('platform.system_alias',
return_value=('darwin', '', '')):
comm_mock = mock.Mock()
comm_attrs = {'communicate.return_value':
('42.42.42', 'error')}
comm_mock.configure_mock(**comm_attrs) # pylint: disable=star-args
popen_mock.return_value = comm_mock
self.assertEqual(get_os_info()[0], 'darwin')
self.assertEqual(get_os_info()[1], '42.42.42')
def test_current_version(self):
current_version = self._call(certbot.__version__)
self.assertTrue(self._call("0.6.0") < current_version)
self.assertTrue(current_version < self._call("99.99.99"))
with mock.patch('platform.system_alias',
return_value=('linux', '', '')):
with mock.patch('platform.linux_distribution',
return_value=('', '', '')):
self.assertEqual(get_python_os_info(), ("linux", ""))
with mock.patch('platform.linux_distribution',
return_value=('testdist', '42', '')):
self.assertEqual(get_python_os_info(), ("testdist", "42"))
with mock.patch('platform.system_alias',
return_value=('freebsd', '9.3-RC3-p1', '')):
self.assertEqual(get_python_os_info(), ("freebsd", "9"))
with mock.patch('platform.system_alias',
return_value=('windows', '', '')):
with mock.patch('platform.win32_ver',
return_value=('4242', '95', '2', '')):
self.assertEqual(get_python_os_info(),
("windows", "95"))
if __name__ == "__main__":

View file

@ -213,9 +213,95 @@ def safely_remove(path):
raise
def get_os_info():
def get_os_info(filepath="/etc/os-release"):
"""
Get OS name and version
:param str filepath: File path of os-release file
:returns: (os_name, os_version)
:rtype: `tuple` of `str`
"""
if os.path.isfile(filepath):
# Systemd os-release parsing might be viable
os_name, os_version = get_systemd_os_info(filepath=filepath)
if os_name:
return (os_name, os_version)
# Fallback to platform module
return get_python_os_info()
def get_os_info_ua(filepath="/etc/os-release"):
"""
Get OS name and version string for User Agent
:param str filepath: File path of os-release file
:returns: os_ua
:rtype: `str`
"""
if os.path.isfile(filepath):
os_ua = _get_systemd_os_release_var("PRETTY_NAME", filepath=filepath)
if not os_ua:
os_ua = _get_systemd_os_release_var("NAME", filepath=filepath)
if os_ua:
return os_ua
# Fallback
return " ".join(get_python_os_info())
def get_systemd_os_info(filepath="/etc/os-release"):
"""
Parse systemd /etc/os-release for distribution information
:param str filepath: File path of os-release file
:returns: (os_name, os_version)
:rtype: `tuple` of `str`
"""
os_name = _get_systemd_os_release_var("ID", filepath=filepath)
os_version = _get_systemd_os_release_var("VERSION_ID", filepath=filepath)
return (os_name, os_version)
def _get_systemd_os_release_var(varname, filepath="/etc/os-release"):
"""
Get single value from systemd /etc/os-release
:param str varname: Name of variable to fetch
:param str filepath: File path of os-release file
:returns: requested value
:rtype: `str`
"""
var_string = varname+"="
if not os.path.isfile(filepath):
return ""
with open(filepath, 'r') as fh:
contents = fh.readlines()
for line in contents:
if line.strip().startswith(var_string):
# Return the value of var, normalized
return _normalize_string(line.strip()[len(var_string):])
return ""
def _normalize_string(orig):
"""
Helper function for _get_systemd_os_release_var() to remove quotes
and whitespaces
"""
return orig.replace('"', '').replace("'", "").strip()
def get_python_os_info():
"""
Get Operating System type/distribution and major version
using python platform module
:returns: (os_name, os_version)
:rtype: `tuple` of `str`
@ -240,7 +326,6 @@ def get_os_info():
["sw_vers", "-productVersion"],
stdout=subprocess.PIPE
).communicate()[0]
os_ver = os_ver.partition(".")[0]
elif os_type.startswith('freebsd'):
# eg "9.3-RC3-p1"
os_ver = os_ver.partition("-")[0]
@ -317,7 +402,7 @@ def enforce_domain_sanity(domain):
domain = domain.encode('ascii').lower()
except UnicodeError:
error_fmt = (u"Internationalized domain names "
"are not presently supported: {0}")
"are not presently supported: {0}")
if isinstance(domain, six.text_type):
raise errors.ConfigurationError(error_fmt.format(domain))
else:
@ -344,7 +429,8 @@ def enforce_domain_sanity(domain):
# first and last char is not "-"
fqdn = re.compile("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,63}$")
if not fqdn.match(domain):
raise errors.ConfigurationError("Requested domain {0} is not a FQDN".format(domain))
raise errors.ConfigurationError("Requested domain {0} is not a FQDN"
.format(domain))
return domain

8
letsencrypt/tests/testdata/os-release vendored Normal file
View file

@ -0,0 +1,8 @@
NAME="SystemdOS"
VERSION="42.42.42 LTS, Unreal"
ID=systemdos
ID_LIKE=debian
PRETTY_NAME="SystemdOS 42.42.42 Unreal"
VERSION_ID="42"
HOME_URL="http://www.example.com/"
SUPPORT_URL="http://help.example.com/"