18687070 pkg install and update times in pkg info output desired
authorChao Pan <chao.pan@oracle.com>
Mon, 20 Apr 2015 17:20:36 -0700
changeset 3193 618a7acf5f09
parent 3191 bf60712eb03a
child 3194 185fd0ebde38
18687070 pkg install and update times in pkg info output desired
doc/client_api_versions.txt
src/client.py
src/modules/api_common.py
src/modules/client/api.py
src/modules/client/client_api.py
src/modules/client/image.py
src/modules/lint/engine.py
src/pkgdep.py
src/sysrepo.py
src/tests/cli/t_pkg_composite.py
src/tests/cli/t_pkg_info.py
src/tests/cli/t_pkg_temp_sources.py
src/tests/pkg5unittest.py
--- a/doc/client_api_versions.txt	Wed Apr 15 19:15:31 2015 -0700
+++ b/doc/client_api_versions.txt	Mon Apr 20 17:20:36 2015 -0700
@@ -1,5 +1,12 @@
+Version 82:
+Compatible with clients using versions 72-81.
+
+    pkg.client.api.PackageInfo has changed as follows:
+        * added a new field 'last_install' to record the package last install time
+        * added a new field 'last_update' to record the package last update time
+
 Version 81:
-Incompatible with clients using versions 72-80.
+Compatible with clients using versions 72-80.
 
     pkg.client.api.ImageInterface has changed as follows:
         * Introduced new non-linked image interfaces:
--- a/src/client.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/client.py	Mon Apr 20 17:20:36 2015 -0700
@@ -93,7 +93,7 @@
         import sys
         sys.exit(1)
 
-CLIENT_API_VERSION = 81
+CLIENT_API_VERSION = 82
 PKG_CLIENT_NAME = "pkg"
 
 JUST_UNKNOWN = 0
--- a/src/modules/api_common.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/modules/api_common.py	Mon Apr 20 17:20:36 2015 -0700
@@ -135,7 +135,7 @@
             version=None, build_release=None, branch=None, packaging_date=None,
             size=None, csize=None, licenses=None, links=None, hardlinks=None,
             files=None, dirs=None, dependencies=None, description=None,
-            attrs=None):
+            attrs=None, last_update=None, last_install=None):
                 self.pkg_stem = pkg_stem
 
                 self.summary = summary
@@ -159,6 +159,8 @@
                 self.dependencies = dependencies
                 self.description = description
                 self.attrs = attrs or {}
+                self.last_update = last_update
+                self.last_install = last_install
 
         def __str__(self):
                 return str(self.fmri)
--- a/src/modules/client/api.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/modules/client/api.py	Mon Apr 20 17:20:36 2015 -0700
@@ -69,6 +69,7 @@
 import time
 import urllib
 
+import pkg.catalog as catalog
 import pkg.client.api_errors as apx
 import pkg.client.bootenv as bootenv
 import pkg.client.history as history
@@ -103,8 +104,8 @@
 # things like help(pkg.client.api.PlanDescription)
 from pkg.client.plandesc import PlanDescription # pylint: disable=W0611
 
-CURRENT_API_VERSION = 81
-COMPATIBLE_API_VERSIONS = frozenset([72, 73, 74, 75, 76, 77, 78, 79, 80,
+CURRENT_API_VERSION = 82
+COMPATIBLE_API_VERSIONS = frozenset([72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
     CURRENT_API_VERSION])
 CURRENT_P5I_VERSION = 1
 
@@ -3297,6 +3298,8 @@
 
                 img_inst_cat = self._img.get_catalog(
                     self._img.IMG_CATALOG_INSTALLED)
+                img_inst_base = img_inst_cat.get_part("catalog.base.C",
+                    must_exist=True)
                 op_time = datetime.datetime.utcnow()
                 pubs = self.__get_temp_repo_pubs(repos)
                 progtrack = self.__progresstracker
@@ -3461,7 +3464,15 @@
                                         # Only the base catalog part stores
                                         # package state information and/or
                                         # other metadata.
-                                        mdata = entry["metadata"] = {}
+                                        mdata = {}
+                                        if installed:
+                                                mdata = dict(
+                                                    img_inst_base.get_entry(
+                                                    pub=pub, stem=stem,
+                                                    ver=ver)["metadata"])
+
+                                        entry["metadata"] = mdata
+
                                         states = [pkgdefs.PKG_STATE_KNOWN,
                                             pkgdefs.PKG_STATE_ALT_SOURCE]
                                         if cat_ver == 0:
@@ -3693,7 +3704,7 @@
         def __get_pkg_list(self, pkg_list, cats=None, collect_attrs=False,
             inst_cat=None, known_cat=None, patterns=misc.EmptyI,
             pubs=misc.EmptyI, raise_unmatched=False, ranked=False, repos=None,
-            return_fmris=False, variants=False):
+            return_fmris=False, return_metadata=False, variants=False):
                 """This is the implementation of get_pkg_list.  The other
                 function is a wrapper that uses locking.  The separation was
                 necessary because of API functions that already perform locking
@@ -4046,7 +4057,8 @@
                         targets = set()
 
                         omit_var = False
-                        states = entry["metadata"]["states"]
+                        mdata = entry["metadata"]
+                        states = mdata["states"]
                         pkgi = pkgdefs.PKG_STATE_INSTALLED in states
                         ddm = lambda: collections.defaultdict(list)
                         attrs = collections.defaultdict(ddm)
@@ -4192,9 +4204,19 @@
                         if return_fmris:
                                 pfmri = fmri.PkgFmri(name=stem, publisher=pub,
                                         version=ver)
-                                yield (pfmri, summ, pcats, states, attrs)
+                                if return_metadata:
+                                        yield (pfmri, summ, pcats, states,
+                                            attrs, mdata)
+                                else:
+                                        yield (pfmri, summ, pcats, states,
+                                            attrs)
                         else:
-                                yield (t, summ, pcats, states, attrs)
+                                if return_metadata:
+                                        yield (t, summ, pcats, states,
+                                            attrs, mdata)
+                                else:
+                                        yield (t, summ, pcats, states,
+                                            attrs)
 
                 if raise_unmatched:
                         # Caller has requested that non-matching patterns or
@@ -4283,11 +4305,13 @@
                 }
 
                 try:
-                        for pfmri, summary, cats, states, attrs in self.__get_pkg_list(
+                        for pfmri, summary, cats, states, attrs, mdata in \
+                            self.__get_pkg_list(
                             ilist, collect_attrs=collect_attrs,
                             inst_cat=inst_cat, known_cat=known_cat,
                             patterns=fmri_strings, raise_unmatched=True,
-                            ranked=ranked, return_fmris=True, variants=True):
+                            ranked=ranked, return_fmris=True,
+                            return_metadata=True, variants=True):
                                 release = build_release = branch = \
                                     packaging_date = None
 
@@ -4383,6 +4407,8 @@
                                         size = csize = 0
 
                                 # Trim response set.
+                                last_install = None
+                                last_update = None
                                 if PackageInfo.STATE in info_needed:
                                         if unsupported is True and \
                                             PackageInfo.UNSUPPORTED not in states:
@@ -4393,6 +4419,13 @@
                                                 states = set(states)
                                                 states.add(
                                                     PackageInfo.UNSUPPORTED)
+
+                                        if "last-update" in mdata:
+                                                last_update = catalog.basic_ts_to_datetime(
+                                                    mdata["last-update"]).strftime("%c")
+                                        if "last-install" in mdata:
+                                                last_install = catalog.basic_ts_to_datetime(
+                                                    mdata["last-install"]).strftime("%c")
                                 else:
                                         states = misc.EmptyI
 
@@ -4412,7 +4445,9 @@
                                     csize=csize, pfmri=pfmri, licenses=licenses,
                                     links=links, hardlinks=hardlinks, files=files,
                                     dirs=dirs, dependencies=dependencies,
-                                    description=description, attrs=attrs))
+                                    description=description, attrs=attrs,
+                                    last_update=last_update,
+                                    last_install=last_install))
                 except apx.InventoryException as e:
                         if e.illegal:
                                 self.log_operation_end(
--- a/src/modules/client/client_api.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/modules/client/client_api.py	Mon Apr 20 17:20:36 2015 -0700
@@ -68,7 +68,7 @@
 from pkg.client.pkgdefs import *
 from pkg.misc import EmptyI, msg, emsg, PipeError
 
-CLIENT_API_VERSION = 81
+CLIENT_API_VERSION = 82
 PKG_CLIENT_NAME = "pkg"
 pkg_timer = pkg.misc.Timer("pkg client")
 SYSREPO_HIDDEN_URI = "<system-repository>"
@@ -2415,6 +2415,12 @@
 
                 __append_attr_lists(_("Branch"), str(pi.branch))
                 __append_attr_lists(_("Packaging Date"), pi.packaging_date)
+                if pi.last_install:
+                        __append_attr_lists(_("Last Install Time"),
+                            pi.last_install)
+                if pi.last_update:
+                        __append_attr_lists(_("Last Update Time"),
+                            pi.last_update)
                 __append_attr_lists(_("Size"), misc.bytes_to_str(pi.size))
                 __append_attr_lists(_("FMRI"),
                     pi.fmri.get_fmri(include_build=False))
--- a/src/modules/client/image.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/modules/client/image.py	Mon Apr 20 17:20:36 2015 -0700
@@ -2465,6 +2465,7 @@
 
                 added = set()
                 removed = set()
+                updated = {}
                 for add_pkg, rem_pkg in pkg_pairs:
                         if add_pkg == rem_pkg:
                                 continue
@@ -2472,6 +2473,10 @@
                                 added.add(add_pkg)
                         if rem_pkg:
                                 removed.add(rem_pkg)
+                        if add_pkg and rem_pkg:
+                                updated[add_pkg] = \
+                                    dict(kcat.get_entry(rem_pkg).get(
+                                    "metadata", {}))
 
                 combo = added.union(removed)
 
@@ -2485,9 +2490,25 @@
                         if pfmri in removed:
                                 icat.remove_package(pfmri)
                                 states.discard(pkgdefs.PKG_STATE_INSTALLED)
+                                mdata.pop("last-install", None)
+                                mdata.pop("last-update", None)
 
                         if pfmri in added:
                                 states.add(pkgdefs.PKG_STATE_INSTALLED)
+                                cur_time = pkg.catalog.now_to_basic_ts()
+                                if pfmri in updated:
+                                        last_install = updated[pfmri].get(
+                                            "last-install")
+                                        if last_install:
+                                                mdata["last-install"] = \
+                                                    last_install
+                                                mdata["last-update"] = \
+                                                    cur_time
+                                        else:
+                                                mdata["last-install"] = \
+                                                    cur_time
+                                else:
+                                        mdata["last-install"] = cur_time
                                 if pkgdefs.PKG_STATE_ALT_SOURCE in states:
                                         states.discard(
                                             pkgdefs.PKG_STATE_UPGRADABLE)
--- a/src/modules/lint/engine.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/modules/lint/engine.py	Mon Apr 20 17:20:36 2015 -0700
@@ -42,7 +42,7 @@
 import urllib2
 
 PKG_CLIENT_NAME = "pkglint"
-CLIENT_API_VERSION = 81
+CLIENT_API_VERSION = 82
 pkg.client.global_settings.client_name = PKG_CLIENT_NAME
 
 class LintEngineException(Exception):
--- a/src/pkgdep.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/pkgdep.py	Mon Apr 20 17:20:36 2015 -0700
@@ -43,7 +43,7 @@
 import pkg.publish.dependencies as dependencies
 from pkg.misc import msg, emsg, PipeError
 
-CLIENT_API_VERSION = 81
+CLIENT_API_VERSION = 82
 PKG_CLIENT_NAME = "pkgdepend"
 
 DEFAULT_SUFFIX = ".res"
--- a/src/sysrepo.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/sysrepo.py	Mon Apr 20 17:20:36 2015 -0700
@@ -59,7 +59,7 @@
 orig_cwd = None
 
 PKG_CLIENT_NAME = "pkg.sysrepo"
-CLIENT_API_VERSION = 81
+CLIENT_API_VERSION = 82
 pkg.client.global_settings.client_name = PKG_CLIENT_NAME
 
 # exit codes
--- a/src/tests/cli/t_pkg_composite.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/tests/cli/t_pkg_composite.py	Mon Apr 20 17:20:36 2015 -0700
@@ -28,7 +28,9 @@
         testutils.setup_environment("../../../proto")
 import pkg5unittest
 
+import json
 import os
+import pkg.catalog as catalog
 import pkg.fmri as fmri
 import pkg.portable as portable
 import pkg.misc as misc
@@ -364,18 +366,27 @@
                 # output.
                 self.pkg("install [email protected]")
                 self.pkg("info")
+
+                path = os.path.join(self.img_path(),
+                    "var/pkg/state/installed/catalog.base.C")
+
+                entry = json.load(open(path))["test"]["foo"][0]
+                pkg_install = catalog.basic_ts_to_datetime(
+                    entry["metadata"]["last-install"]).strftime("%c")
                 expected = """\
-          Name: foo
-       Summary: Example package foo.
-         State: Installed
-     Publisher: test
-       Version: 1.0
-        Branch: None
-Packaging Date: {pkg_date}
-          Size: 41.00 B
-          FMRI: {pkg_fmri}
+             Name: foo
+          Summary: Example package foo.
+            State: Installed
+        Publisher: test
+          Version: 1.0
+           Branch: None
+   Packaging Date: {pkg_date}
+Last Install Time: {pkg_install}
+             Size: 41.00 B
+             FMRI: {pkg_fmri}
 """.format(pkg_date=self.foo10.version.get_timestamp().strftime("%c"),
-    pkg_fmri=self.foo10.get_fmri(include_build=False))
+    pkg_fmri=self.foo10.get_fmri(include_build=False),
+    pkg_install=pkg_install)
                 self.assertEqualDiff(expected, self.output)
 
         def test_02_contents(self):
--- a/src/tests/cli/t_pkg_info.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/tests/cli/t_pkg_info.py	Mon Apr 20 17:20:36 2015 -0700
@@ -27,10 +27,12 @@
         testutils.setup_environment("../../../proto")
 import pkg5unittest
 
+import json
 import os
 import shutil
 import unittest
 
+import pkg.catalog as catalog
 import pkg.actions as actions
 import pkg.fmri as fmri
 
@@ -745,6 +747,34 @@
                 self.pkg("info -r --license [email protected]")
                 self.assertEqual("tmp/copyright0\n", self.output)
 
+        def test_info_update_install(self):
+                """Test that pkg info will show last update and install time"""
+
+                os.environ["LC_ALL"] = "C"
+                self.image_create(self.rurl)
+                self.pkg("install [email protected]")
+                path = os.path.join(self.img_path(),
+                    "var/pkg/state/installed/catalog.base.C")
+                entry = json.load(open(path))["test"]["bronze"][0]["metadata"]
+                last_install = catalog.basic_ts_to_datetime(
+                    entry["last-install"]).strftime("%c")
+                self.pkg(("info bronze | grep 'Last Install Time: "
+                    "{0}'").format(last_install))
+
+                # Now update the version.
+                self.pkg("install [email protected]")
+                entry = json.load(open(path))["test"]["bronze"][0]["metadata"]
+                last_install = catalog.basic_ts_to_datetime(
+                    entry["last-install"]).strftime("%c")
+                self.pkg(("info bronze | grep 'Last Install Time: "
+                    "{0}'").format(last_install))
+
+                # Last update should be existed this time.
+                last_update = catalog.basic_ts_to_datetime(
+                    entry["last-update"]).strftime("%c")
+                self.pkg(("info bronze | grep 'Last Update Time: "
+                    "{0}'").format(last_update))
+
 
 class TestPkgInfoPerTestRepo(pkg5unittest.SingleDepotTestCase):
         """A separate test class is needed because these tests modify packages
--- a/src/tests/cli/t_pkg_temp_sources.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/tests/cli/t_pkg_temp_sources.py	Mon Apr 20 17:20:36 2015 -0700
@@ -29,7 +29,9 @@
         testutils.setup_environment("../../../proto")
 import pkg5unittest
 
+import json
 import os
+import pkg.catalog as catalog
 import pkg.fmri as fmri
 import pkg.portable as portable
 import pkg.misc as misc
@@ -556,18 +558,27 @@
                 # output.
                 self.pkg("install -g {0} [email protected]".format(self.foo_arc))
                 self.pkg("info")
+
+                os.environ["LC_ALL"] = "C"
+                path = os.path.join(self.img_path(),
+                    "var/pkg/state/installed/catalog.base.C")
+                entry = json.load(open(path))["test"]["foo"][0]["metadata"]
+                pkg_install = catalog.basic_ts_to_datetime(
+                    entry["last-install"]).strftime("%c")
+
                 expected = """\
-          Name: foo
-       Summary: Example package foo.
-         State: Installed
-     Publisher: test
-       Version: 1.0
-        Branch: None
-Packaging Date: {pkg_date}
-          Size: 41.00 B
-          FMRI: {pkg_fmri}
+             Name: foo
+          Summary: Example package foo.
+            State: Installed
+        Publisher: test
+          Version: 1.0
+           Branch: None
+   Packaging Date: {pkg_date}
+Last Install Time: {pkg_install}
+             Size: 41.00 B
+             FMRI: {pkg_fmri}
 """.format(pkg_date=pd(self.foo10), pkg_fmri=self.foo10.get_fmri(
-    include_build=False))
+    include_build=False), pkg_install=pkg_install)
                 self.assertEqualDiff(expected, self.output)
 
                 # Verify that when showing package info from archive that
--- a/src/tests/pkg5unittest.py	Wed Apr 15 19:15:31 2015 -0700
+++ b/src/tests/pkg5unittest.py	Mon Apr 20 17:20:36 2015 -0700
@@ -136,7 +136,7 @@
 
 # Version test suite is known to work with.
 PKG_CLIENT_NAME = "pkg"
-CLIENT_API_VERSION = 81
+CLIENT_API_VERSION = 82
 
 ELIDABLE_ERRORS = [ TestSkippedException, depotcontroller.DepotStateException ]