Merge lp:~raharper/curtin/trunk.curtin-announce into lp:~curtin-dev/curtin/trunk

Proposed by Ryan Harper
Status: Merged
Approved by: Scott Moser
Approved revision: 434
Merged at revision: 448
Proposed branch: lp:~raharper/curtin/trunk.curtin-announce
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 280 lines (+179/-1)
8 files modified
curtin/__init__.py (+2/-0)
curtin/commands/main.py (+6/-1)
curtin/pack.py (+1/-0)
curtin/version.py (+52/-0)
debian/rules (+6/-0)
tests/unittests/test_version.py (+87/-0)
tests/vmtests/__init__.py (+18/-0)
tests/vmtests/test_basic.py (+7/-0)
To merge this branch: bzr merge lp:~raharper/curtin/trunk.curtin-announce
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
curtin developers Pending
Review via email: mp+306358@code.launchpad.net

Commit message

curtin: add version module and display in output and logs

It's difficult to determine what curtin version is running
from the collected logs in a maas deployment which uses
curtin by sending it to the target system. Resolve this by
having curtin announce itself.

- Introduce a new version module which does the following:
  - search for a .version file in the curtin PYTHONPATH
    if present and use that file contents for version
  - if no .version file is found, test if we're in-tree
    and use the existing curtin.__version__ value plus the
    bzr revno
  - if neither are found, just use the curtin.__version__ value
- Modify curtin.command.main to print the version string in argparse
  output
- Update curtin logging setup to emit a version string as soon as logging
  is configured

Description of the change

curtin: add version module and display in output and logs

It's difficult to determine what curtin version is running
from the collected logs in a maas deployment which uses
curtin by sending it to the target system. Resolve this by
having curtin announce itself.

- Introduce a new version module which does the following:
  - search for a .version file in the curtin PYTHONPATH
    if present and use that file contents for version
  - if no .version file is found, test if we're in-tree
    and use the existing curtin.__version__ value plus the
    bzr revno
  - if neither are found, just use the curtin.__version__ value
- Modify curtin.command.main to print the version string in argparse
  output
- Update curtin logging setup to emit a version string as soon as logging
  is configured

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

It seems like a generally nice idea. a couple of things though

I think it'd be good to get the dpkg info if it looks like it is installed.
possibly if the __file__ here is in some set of expected install paths... the problem with that is that dpkg -S 'filename' is heavy, and requires dpkg.

Alternatively, and maybe better, we could have the package install a file that states its version.
and then just read that.

Revision history for this message
Ryan Harper (raharper) wrote :

On Thu, Jan 19, 2017 at 12:30 PM, Scott Moser <email address hidden> wrote:

> It seems like a generally nice idea. a couple of things though
>
> I think it'd be good to get the dpkg info if it looks like it is installed.
> possibly if the __file__ here is in some set of expected install paths...
> the problem with that is that dpkg -S 'filename' is heavy, and requires
> dpkg.
>
> Alternatively, and maybe better, we could have the package install a file
> that states its version.
> and then just read that.
>
> Diff comments:
>
> >
> > === added file 'curtin/version.py'
> > --- curtin/version.py 1970-01-01 00:00:00 +0000
> > +++ curtin/version.py 2016-09-21 16:08:03 +0000
> > @@ -0,0 +1,40 @@
> > +from curtin import __version__ as old_version
> > +import os
> > +import subprocess
> > +
> > +
> > +def version_string():
> > + """ Extract a version string from curtin source or version file"""
> > + def _find_path(pathfile):
> > + """ Check for file existance and return dirpath
> > + Search PYTHONPATH, as curtin is typically
> > + launched with PYTHONPATH set, as with curtin pack
> > + executables.
> > + """
> > + if os.path.exists(pathfile):
> > + return os.getcwd()
> > + elif 'PYTHONPATH' in os.environ:
> > + for path in os.environ['PYTHONPATH'].split(":"):
> > + curpath = os.path.join(path, pathfile)
> > + if os.path.exists(curpath):
> > + return os.path.dirname(curpath)
> > +
> > + return None
> > +
>
> any reason not to use __file__ ? rather than PYTHONPATH ? python path just
> seems like it might go wrong.
>

I didn't know of __file__; fixing.

>
> > + dotversion = '.version'
> > + dotpath = _find_path(dotversion)
> > + if dotpath:
> > + return open(os.path.join(dotpath, dotversion),
> 'r').read().strip()
> > +
> > + bzrdir = _find_path('.bzr')
> > + revno = None
> > + if bzrdir:
> > + os.chdir(bzrdir)
> > + out = subprocess.check_output(['bzr', 'revno'])
> > + revno = "bzr%s" % out.decode('ascii').strip()
>
> any reason for ascii ?
>

Not sure why. I can 'utf-8' instead.

> Again here, i think better to just use the path that we know. .bzr will
> be 2 dirs up from this file name .
>

If I use __file__ above, then we get that.

>
> and we need to catch an exception here if there is a bzr dir but no bzr
> command.
>

Are there really .bzr dirs in a orig tarball? Otherwise, I don't see how
we'd have curtin source without bzr?

>
> > +
> > + version = old_version
> > + if revno:
> > + version += "~%s" % revno
> > +
> > + return version
>
>
> --
> https://code.launchpad.net/~raharper/curtin/trunk.curtin-
> announce/+merge/306358
> You are the owner of lp:~raharper/curtin/trunk.curtin-announce.
>

428. By Ryan Harper

Use __file__, catch subprocess exception; export LANG into install log

429. By Ryan Harper

merge from trunk

430. By Ryan Harper

merge from lp:~smoser/curtin/trunk.curtin-announce

431. By Ryan Harper

pep fix for merge

432. By Ryan Harper

version: drop .version file; add dpkg-version, unittests

433. By Ryan Harper

merge from trunk

434. By Ryan Harper

merge from trunk

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'curtin/__init__.py'
--- curtin/__init__.py 2016-08-18 16:02:27 +0000
+++ curtin/__init__.py 2017-02-06 22:02:13 +0000
@@ -35,6 +35,8 @@
35 'SUBCOMMAND_SYSTEM_UPGRADE',35 'SUBCOMMAND_SYSTEM_UPGRADE',
36 # supports new format of apt configuration36 # supports new format of apt configuration
37 'APT_CONFIG_V1',37 'APT_CONFIG_V1',
38 # has version module
39 'HAS_VERSION_MODULE',
38]40]
3941
40__version__ = "0.1.0"42__version__ = "0.1.0"
4143
=== modified file 'curtin/commands/main.py'
--- curtin/commands/main.py 2016-08-10 21:44:19 +0000
+++ curtin/commands/main.py 2017-02-06 22:02:13 +0000
@@ -24,6 +24,9 @@
24from .. import log24from .. import log
25from .. import util25from .. import util
26from ..deps import install_deps26from ..deps import install_deps
27import curtin.version
28
29VERSIONSTR = curtin.version.version_string()
2730
28SUB_COMMAND_MODULES = [31SUB_COMMAND_MODULES = [
29 'apply_net', 'block-info', 'block-meta', 'block-wipe', 'curthooks',32 'apply_net', 'block-info', 'block-meta', 'block-wipe', 'curthooks',
@@ -57,7 +60,7 @@
5760
58def get_main_parser(stacktrace=False, verbosity=0,61def get_main_parser(stacktrace=False, verbosity=0,
59 parser_class=argparse.ArgumentParser):62 parser_class=argparse.ArgumentParser):
60 parser = parser_class(prog='curtin')63 parser = parser_class(prog='curtin', epilog='Version %s' % VERSIONSTR)
61 parser.add_argument('--showtrace', action='store_true', default=stacktrace)64 parser.add_argument('--showtrace', action='store_true', default=stacktrace)
62 parser.add_argument('-v', '--verbose', action='count', default=verbosity,65 parser.add_argument('-v', '--verbose', action='count', default=verbosity,
63 dest='verbosity')66 dest='verbosity')
@@ -180,6 +183,8 @@
180 sys.exit(1)183 sys.exit(1)
181184
182 log.basicConfig(stream=args.log_file, verbosity=verbosity)185 log.basicConfig(stream=args.log_file, verbosity=verbosity)
186 log.LOG.error('curtin v. %s started' % VERSIONSTR)
187 log.LOG.error('LANG=%s', os.environ.get('LANG'))
183188
184 paths = util.get_paths()189 paths = util.get_paths()
185190
186191
=== modified file 'curtin/pack.py'
--- curtin/pack.py 2015-12-17 16:19:56 +0000
+++ curtin/pack.py 2017-02-06 22:02:13 +0000
@@ -140,6 +140,7 @@
140 shutil.copytree(paths['helpers'], os.path.join(exdir, "helpers"))140 shutil.copytree(paths['helpers'], os.path.join(exdir, "helpers"))
141 shutil.copytree(paths['lib'], os.path.join(exdir, "curtin"),141 shutil.copytree(paths['lib'], os.path.join(exdir, "curtin"),
142 ignore=not_dot_py)142 ignore=not_dot_py)
143
143 write_exe_wrapper(entrypoint='curtin.commands.main',144 write_exe_wrapper(entrypoint='curtin.commands.main',
144 path=os.path.join(bindir, 'curtin'),145 path=os.path.join(bindir, 'curtin'),
145 deps_check_entry="curtin.deps.check")146 deps_check_entry="curtin.deps.check")
146147
=== added file 'curtin/version.py'
--- curtin/version.py 1970-01-01 00:00:00 +0000
+++ curtin/version.py 2017-02-06 22:02:13 +0000
@@ -0,0 +1,52 @@
1from curtin import __version__ as old_version
2import os
3import subprocess
4
5_PACKAGED_VERSION = '@@PACKAGED_VERSION@@'
6
7
8def version_string():
9 """ Extract a version string from curtin source or version file"""
10 def _find_path(pathfile):
11 """ Check for file existance and return dirpath
12 Search PYTHONPATH, as curtin is typically
13 launched with PYTHONPATH set, as with curtin pack
14 executables.
15 """
16 if os.path.exists(pathfile):
17 return os.getcwd()
18 else:
19 path = os.path.abspath(os.path.join(
20 __file__, '..', '..'))
21 curpath = os.path.join(path, pathfile)
22 if os.path.exists(curpath):
23 return os.path.dirname(curpath)
24
25 return None
26
27 if not _PACKAGED_VERSION.startswith('@@'):
28 return _PACKAGED_VERSION
29
30 bzrdir = _find_path('.bzr')
31 revno = dpkg_version = None
32 if bzrdir:
33 try:
34 out = subprocess.check_output(['bzr', 'revno'], cwd=bzrdir)
35 revno = "bzr%s" % out.decode('utf-8').strip()
36 except subprocess.CalledProcessError:
37 pass
38 else:
39 try:
40 out = subprocess.check_output(['dpkg-query', '--show',
41 '--showformat', '${Version}',
42 'curtin-common'])
43 dpkg_version = out.decode('utf-8').strip()
44 except subprocess.CalledProcessError:
45 pass
46 version = old_version
47 if revno:
48 version += "~%s" % revno
49 if dpkg_version:
50 version = dpkg_version
51
52 return version
053
=== modified file 'debian/rules'
--- debian/rules 2013-09-20 20:55:05 +0000
+++ debian/rules 2017-02-06 22:02:13 +0000
@@ -3,6 +3,10 @@
3PYVERS := $(shell pyversions -r)3PYVERS := $(shell pyversions -r)
4PY3VERS := $(shell py3versions -r)4PY3VERS := $(shell py3versions -r)
55
6DEB_VERSION := $(shell dpkg-parsechangelog --show-field=Version)
7UPSTREAM_VERSION := $(shell x="$(DEB_VERSION)"; echo "$${x%-*}")
8PKG_VERSION := $(shell x="$(DEB_VERSION)"; echo "$${x\#\#*-}")
9
6%:10%:
7 dh $@ --with=python2,python311 dh $@ --with=python2,python3
812
@@ -13,3 +17,5 @@
13 $$python setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb; \17 $$python setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb; \
14 done18 done
15 chmod 755 $(CURDIR)/debian/tmp/usr/lib/curtin/helpers/*19 chmod 755 $(CURDIR)/debian/tmp/usr/lib/curtin/helpers/*
20 find $(CURDIR)/debian/tmp
21 for f in $$(find $(CURDIR)/debian/tmp/usr/lib -type f -name version.py); do [ -f "$$f" ] || continue; sed -i 's,@@PACKAGED_VERSION@@,$(DEB_VERSION),' "$$f"; done
1622
=== added file 'tests/unittests/test_version.py'
--- tests/unittests/test_version.py 1970-01-01 00:00:00 +0000
+++ tests/unittests/test_version.py 2017-02-06 22:02:13 +0000
@@ -0,0 +1,87 @@
1from unittest import TestCase
2import mock
3import subprocess
4import os
5
6from curtin import version
7from curtin import __version__ as old_version
8
9
10class CurtinVersionBase(TestCase):
11 def setUp(self):
12 super(CurtinVersionBase, self).setUp()
13
14 def add_patch(self, target, attr):
15 """Patches specified target object and sets it as attr on test
16 instance also schedules cleanup"""
17 m = mock.patch(target, autospec=True)
18 p = m.start()
19 self.addCleanup(m.stop)
20 setattr(self, attr, p)
21
22
23class TestCurtinVersion(CurtinVersionBase):
24
25 def setUp(self):
26 self.add_patch('subprocess.check_output', 'mock_subp')
27 self.add_patch('os.path', 'mock_path')
28
29 @mock.patch.object(os, 'getcwd')
30 def test_packaged_version(self, mock_getcwd):
31 original_pkg_string = version._PACKAGED_VERSION
32 test_pkg_version = '9.8.7-curtin-0ubuntu12'
33 version._PACKAGED_VERSION = test_pkg_version
34
35 ver_string = version.version_string()
36
37 # check we got the packaged version string set
38 self.assertEqual(test_pkg_version, ver_string)
39 # make sure we didn't take any other path
40 self.assertEqual([], self.mock_path.call_args_list)
41 self.assertEqual([], mock_getcwd.call_args_list)
42 self.assertEqual([], self.mock_subp.call_args_list)
43
44 version._PACKAGED_VERSION = original_pkg_string
45
46 def test_bzr_revno_version(self):
47 self.mock_path.exists.return_value = True
48 bzr_revno = "999"
49 self.mock_subp.return_value = bzr_revno.encode("utf-8")
50
51 ver_string = version.version_string()
52 self.assertEqual(old_version + "~bzr" + bzr_revno, ver_string)
53
54 @mock.patch.object(os, 'getcwd')
55 def test_bzr_revno_version_exception(self, mock_getcwd):
56 self.mock_path.exists.return_value = True
57 mock_getcwd.return_value = "/tmp/foo"
58 self.mock_subp.side_effect = subprocess.CalledProcessError(1, 'foo')
59
60 ver_string = version.version_string()
61 self.assertEqual(old_version, ver_string)
62
63 def test_dpkg_version(self):
64 self.mock_path.exists.return_value = False
65 dpkg_version = "7.12.23_curtin_2ubunt29"
66 self.mock_subp.return_value = dpkg_version.encode("utf-8")
67
68 ver_string = version.version_string()
69 self.assertEqual(dpkg_version, ver_string)
70
71 def test_dpkg_version_exception(self):
72 self.mock_path.exists.return_value = True
73 self.mock_subp.side_effect = subprocess.CalledProcessError(1, '')
74
75 ver_string = version.version_string()
76 self.assertEqual(old_version, ver_string)
77
78 def test_old_version(self):
79 self.mock_path.exists.return_value = False
80 self.mock_subp.return_value = "".encode('utf-8')
81
82 ver_string = version.version_string()
83
84 self.assertEqual(old_version, ver_string)
85
86
87# vi: ts=4 expandtab syntax=python
088
=== modified file 'tests/vmtests/__init__.py'
--- tests/vmtests/__init__.py 2017-02-06 20:29:33 +0000
+++ tests/vmtests/__init__.py 2017-02-06 22:02:13 +0000
@@ -13,6 +13,7 @@
13import yaml13import yaml
14import curtin.net as curtin_net14import curtin.net as curtin_net
15import curtin.util as util15import curtin.util as util
16import curtin.version
1617
17from curtin.commands.install import INSTALL_PASS_MSG18from curtin.commands.install import INSTALL_PASS_MSG
1819
@@ -748,6 +749,23 @@
748 with open(os.path.join(self.td.collect, filename), mode) as fp:749 with open(os.path.join(self.td.collect, filename), mode) as fp:
749 return fp.read()750 return fp.read()
750751
752 def load_log_file(self, filename):
753 with open(filename, 'rb') as l:
754 return l.read().decode('utf-8', errors='replace')
755
756 def get_install_log_curtin_version(self):
757 # "curtin v. 0.1.0~bzr426 started"
758 install_log = self.load_log_file(self.install_log)
759 curtin_vers = [line for line in install_log.splitlines()
760 if 'curtin v.' in line and line.endswith('started')]
761 for ver in curtin_vers:
762 version = ver.split('curtin v. ')[-1].split(' started')[0]
763 if len(version) > 0:
764 return version
765
766 def get_curtin_version(self):
767 return curtin.version.version_string()
768
751 def check_file_strippedline(self, filename, search):769 def check_file_strippedline(self, filename, search):
752 lines = self.load_collect_file(filename).splitlines()770 lines = self.load_collect_file(filename).splitlines()
753 self.assertIn(search, [i.strip() for i in lines])771 self.assertIn(search, [i.strip() for i in lines])
754772
=== modified file 'tests/vmtests/test_basic.py'
--- tests/vmtests/test_basic.py 2016-08-25 23:32:11 +0000
+++ tests/vmtests/test_basic.py 2017-02-06 22:02:13 +0000
@@ -122,6 +122,13 @@
122 # no proxy, so the output of apt-config dump should be empty122 # no proxy, so the output of apt-config dump should be empty
123 self.assertEqual("", apt_proxy_found)123 self.assertEqual("", apt_proxy_found)
124124
125 def test_curtin_install_version(self):
126 installed_version = self.get_install_log_curtin_version()
127 print('Install log version: %s' % installed_version)
128 source_version = self.get_curtin_version()
129 print('Source repo version: %s' % source_version)
130 self.assertEqual(source_version, installed_version)
131
125132
126class PreciseTestBasic(relbase.precise, TestBasicAbs):133class PreciseTestBasic(relbase.precise, TestBasicAbs):
127 __test__ = True134 __test__ = True

Subscribers

People subscribed via source and target branches