735 install -n uninformative
authorDan Price <dp@eng.sun.com>
Wed, 16 Apr 2008 14:39:01 -0700
changeset 329 f549eab0d7b7
parent 328 83af2c933642
child 330 5619f72b91c2
735 install -n uninformative 1118 pkg list -a is way way too slow 1192 ipkg brand is broken in RC0 -- config.xml/platform.xml out of sync with Nevada 1193 snapshot/clone logic in client.py declares a mulligan 1201 manifest filters don't seem to be working properly, can't pkg verify a zone 1206 driver.py has some lurking syntactic issues 1242 ipkg brand: Add NFS and autofs to list of packages which zones deliver by default 1243 modify test cases to reflect 1162 (image-update should refresh...)
src/brand/config.xml
src/brand/pkgcreatezone
src/brand/platform.xml
src/client.py
src/modules/actions/driver.py
src/modules/client/bootenv.py
src/modules/client/image.py
src/modules/client/imageplan.py
src/modules/fmri.py
src/tests/cli/t_upgrade.py
--- a/src/brand/config.xml	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/brand/config.xml	Wed Apr 16 14:39:01 2008 -0700
@@ -47,6 +47,7 @@
 	<postinstall></postinstall>
 
 	<privilege set="default" name="contract_event" />
+	<privilege set="default" name="contract_identity" />
 	<privilege set="default" name="contract_observer" />
 	<privilege set="default" name="file_chown" />
 	<privilege set="default" name="file_chown_self" />
--- a/src/brand/pkgcreatezone	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/brand/pkgcreatezone	Wed Apr 16 14:39:01 2008 -0700
@@ -72,10 +72,21 @@
 
 trap trap_cleanup INT
 
-authority="opensolaris.org=http://pkg.opensolaris.org"
 zonename=""
 zonepath=""
 
+#
+# If there's a preferred authority set for the system, set that as our
+# default.  Otherwise use opensolaris.org.
+#
+authority="opensolaris.org=http://pkg.opensolaris.org"
+if [[ -x /usr/bin/pkg ]]; then
+	sysauth=`LC_ALL=C /usr/bin/pkg authority | grep preferred | awk '{printf "%s=%s", $1, $3}'`
+	if [[ $? -eq 0 && -n "$sysauth" ]]; then
+		authority=$sysauth
+	fi
+fi
+
 # Setup i18n output
 TEXTDOMAIN="SUNW_OST_OSCMD"
 export TEXTDOMAIN
@@ -147,6 +158,12 @@
 pkglist="$pkglist SUNWnis SUNWlldap"
 
 #
+# Get nfs client and autofs; it's a pain not to have them.
+#
+pkglist="$pkglist SUNWnfsc SUNWnfsckr SUNWatfs"
+
+
+#
 # Get man(1) but not the man pages
 #
 pkglist="$pkglist SUNWdoc"
--- a/src/brand/platform.xml	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/brand/platform.xml	Wed Apr 16 14:39:01 2008 -0700
@@ -60,6 +60,8 @@
 	<device match="log" />
 	<device match="logindmux" />
 	<device match="null" />
+	<device match="nsmb" />
+	<device match="net/*" />
 	<device match="openprom" arch="sparc" />
 	<device match="poll" />
 	<device match="pool" />
@@ -103,6 +105,7 @@
 	<device match="rawip" ip-type="exclusive" />
 	<device match="rawip6" ip-type="exclusive" />
 	<device match="rts" ip-type="exclusive" />
+	<device match="sad/admin" ip-type="exclusive" />
 	<device match="sctp" ip-type="exclusive" />
 	<device match="sctp6" ip-type="exclusive" />
 	<device match="spdsock" ip-type="exclusive" />
--- a/src/client.py	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/client.py	Wed Apr 16 14:39:01 2008 -0700
@@ -350,31 +350,52 @@
                         return 1
                 else:
                         return 3
-        try:
-                be = bootenv.BootEnv(img.get_root())
-        except RuntimeError:
-                be = bootenv.BootEnvNull(img.get_root())
 
         # Reload catalog.  This picks up the update from retrieve_catalogs.
         img.load_catalogs(progresstracker)
 
         pkg_list = [ ipkg.get_pkg_stem() for ipkg in img.gen_installed_pkgs() ]
 
+        try:
+                img.make_install_plan(pkg_list, progresstracker, verbose = verbose,
+                    noexecute = noexecute)
+        except RuntimeError, e:
+                error(_("image-update failed: %s") % e)
+
+        assert img.imageplan
+
+        if img.imageplan.nothingtodo():
+                print _("No updates available for this image.")
+                return 0
+
+        if noexecute:
+                return 0
+
+        try:
+                be = bootenv.BootEnv(img.get_root())
+        except RuntimeError:
+                be = bootenv.BootEnvNull(img.get_root())
+
         be.init_image_recovery(img)
 
         try:
-                img.list_install(pkg_list, progresstracker, verbose = verbose,
-                    noexecute = noexecute)
+                img.imageplan.execute()
+                be.activate_image()
+                ret_code = 0
         except RuntimeError, e:
                 error(_("image_update failed: %s") % e)
                 be.restore_image()
                 ret_code = 1
-        else:
-                be.activate_image()
-                ret_code = 0
+        except Exception, e:
+                error(_("An unexpected error happened during image-update: %s") % e)
+                be.restore_image()
+                img.cleanup_downloads()
+                raise
+
         img.cleanup_downloads()
         return ret_code
 
+
 def install(img, args):
         """Attempt to take package specified to INSTALLED state.  The operands
         are interpreted as glob patterns."""
@@ -403,11 +424,6 @@
         if verbose and quiet:
                 usage(_("install: -v and -q may not be combined"))
 
-        try:
-                be = bootenv.BootEnv(img.get_root())
-        except RuntimeError:
-                be = bootenv.BootEnvNull(img.get_root())
-                
         progresstracker = get_tracker(quiet)
 
         img.load_catalogs(progresstracker)
@@ -416,23 +432,48 @@
             for pat in pargs ]
 
         try:
-                img.list_install(pkg_list, progresstracker, filters = filters,
+                img.make_install_plan(pkg_list, progresstracker, filters = filters,
                     verbose = verbose, noexecute = noexecute)
         except RuntimeError, e:
                 error(_("install failed: %s") % e)
+                return 1
+
+        assert img.imageplan
+
+        #
+        # The result of make_install_plan is that an imageplan is now filled out
+        # for the image.
+        #
+        if img.imageplan.nothingtodo():
+                print _("Nothing to install in this image (is this package already installed?)")
+                return 0
+
+        if noexecute:
+                return 0
+
+        try:
+                be = bootenv.BootEnv(img.get_root())
+        except RuntimeError:
+                be = bootenv.BootEnvNull(img.get_root())
+
+        try:
+                img.imageplan.execute()
+                be.activate_install_uninstall()
+                ret_code = 0
+        except RuntimeError, e:
+                error(_("installation failed: %s") % e)
                 be.restore_install_uninstall()
                 ret_code = 1
-        except:
+        except Exception, e:
+                error(_("An unexpected error happened during installation: %s") % e)
                 be.restore_install_uninstall()
                 img.cleanup_downloads()
                 raise
-        else:
-                be.activate_install_uninstall()
-                ret_code = 0
 
         img.cleanup_downloads()   
         return ret_code
 
+
 def uninstall(img, args):
         """Attempt to take package specified to DELETED state."""
 
@@ -454,11 +495,6 @@
 
         progresstracker = get_tracker(quiet)
 
-        try:
-                be = bootenv.BootEnv(img.get_root())
-        except RuntimeError:
-                be = bootenv.BootEnvNull(img.get_root())
-               
         img.load_catalogs(progresstracker)
 
         ip = imageplan.ImagePlan(img, progresstracker, recursive_removal)
@@ -482,15 +518,20 @@
                         error(_("'%s' matches multiple packages") % ppat)
                         for k in pnames:
                                 print "\t%s" % k
+                        err = 1
                         continue
 
                 if len(pnames) < 1:
                         error(_("'%s' matches no installed packages") % \
                             ppat)
+                        err = 1
                         continue
 
                 ip.propose_fmri_removal(pnames[0])
 
+        if err == 1:
+                return err
+
         if verbose:
                 print _("Before evaluation:")
                 print ip
@@ -502,12 +543,31 @@
                 print _("After evaluation:")
                 ip.display()
 
-        if not noexecute:
+        assert not ip.nothingtodo()
+
+        if noexecute:
+                return 0
+
+	try:
+		be = bootenv.BootEnv(img.get_root())
+	except RuntimeError:
+		be = bootenv.BootEnvNull(img.get_root())
+               
+	try:
                 ip.execute()
-                if ip.state == imageplan.EXECUTED_OK:
-                        be.activate_install_uninstall()
-                else:
-                        be.restore_install_uninstall()
+        except RuntimeError, e:
+                error(_("installation failed: %s") % e)
+                be.restore_install_uninstall()
+                ret_code = 1
+	except:
+                error(_("An unexpected error happened during uninstallation: %s") % e)
+		be.restore_install_uninstall()
+		raise
+
+	if ip.state == imageplan.EXECUTED_OK:
+		be.activate_install_uninstall()
+	else:
+		be.restore_install_uninstall()
 
         return err
 
@@ -556,12 +616,21 @@
         retcode = 1
 
         try:
-                for index, fmri, action, value in itertools.chain(*searches):
+                first = True
+                for index, mfmri, action, value in itertools.chain(*searches):
                         retcode = 0
+                        if first:
+                                if action and value:
+                                        print "%-10s %-9s %-25s %s" % ("INDEX",
+                                            "ACTION", "VALUE", "PACKAGE")
+                                else:
+                                        print "%-10s %s" % ("INDEX", "PACKAGE")
+                                first = False
                         if action and value:
-                                print index, action, value, fmri
+                                print "%-10s %-9s %-25s %s" % (index, action,
+                                    value, fmri.PkgFmri(str(mfmri)).get_short_fmri())
                         else:
-                                print index, fmri
+                                print "%-10s %s" % (index, mfmri)
 
         except RuntimeError, failed:
                 print >> sys.stderr, "Some servers failed to respond:"
@@ -753,7 +822,7 @@
         # XXX Need remote-info option, to request equivalent information
         # from repository.
 
-        opts, pargs = getopt.getopt(args, "Ho:s:t:m")
+        opts, pargs = getopt.getopt(args, "Ho:s:t:mf")
 
         valid_special_attrs = [ "action.name", "action.key", "action.raw",
             "pkg.name", "pkg.fmri", "pkg.shortfmri", "pkg.authority",
@@ -761,6 +830,7 @@
 
         display_headers = True
         display_raw = False
+        display_nofilters = False
         attrs = []
         sort_attrs = []
         action_types = []
@@ -775,6 +845,9 @@
                         action_types.extend(arg.split(","))
                 elif opt == "-m":
                         display_raw = True
+                elif opt == "-f":
+                        # Undocumented, for now.
+                        display_nofilters = True
 
         if display_raw:
                 display_headers = False
@@ -820,7 +893,8 @@
                 else:
                         sort_attrs = attrs[:1]
 
-        manifests = ( img.get_manifest(f, filtered = True) for f in fmris )
+        filt = not display_nofilters
+        manifests = ( img.get_manifest(f, filtered = filt) for f in fmris )
 
         actionlist = [ (m, a)
                     for m in manifests
--- a/src/modules/actions/driver.py	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/actions/driver.py	Wed Apr 16 14:39:01 2008 -0700
@@ -374,7 +374,7 @@
                         dcf = file(os.path.normpath(os.path.join(
                             img.get_root(), "etc/driver_classes")))
                 except IOError, e:
-                        e.args += ("etc/driver_classes",e)
+                        e.args += ("etc/driver_classes",)
                         if collect_errs:
                                 errors.append(e)
                         else:
@@ -392,7 +392,7 @@
                         dmf = file(os.path.normpath(os.path.join(
                             img.get_root(), "etc/minor_perm")))
                 except IOError, e:
-                        e.args += ("etc/minor_perm")
+                        e.args += ("etc/minor_perm",)
                         if collect_errs:
                                 errors.append(e)
                         else:
@@ -478,9 +478,9 @@
                     self.__get_image_data(img, name, collect_errs = True)
 
                 for i, err in enumerate(errors):
-                        if err.isinstance(IOError):
+                        if isinstance(err, IOError):
                                 errors[i] = "%s: %s" % (err.args[2], err)
-                        elif err.isinstance(RuntimeError):
+                        elif isinstance(err, RuntimeError):
                                 errors[i] = "etc/name_to_major: more than " \
                                     "one entry for '%s' is present" % name
 
--- a/src/modules/client/bootenv.py	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/client/bootenv.py	Wed Apr 16 14:39:01 2008 -0700
@@ -66,6 +66,10 @@
 
                 self.beList = be.beList()
 
+                # Happens e.g. in zones (at least, for now)
+                if not self.beList:
+                        raise RuntimeError, "nobootenvironments"
+
                 # Need to find the name of the BE we're operating on in order
                 # to create a snapshot and/or a clone of the BE.
 
--- a/src/modules/client/image.py	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/client/image.py	Wed Apr 16 14:39:01 2008 -0700
@@ -444,9 +444,11 @@
 
                 return False
 
-        def _fetch_manifest(self, fmri, filtered):
+        def _fetch_manifest(self, fmri):
                 """Perform steps necessary to get manifest from remote host
-                and write resulting contents to disk."""
+                and write resulting contents to disk.  Helper routine for
+                get_manifest.  Does not filter the results, caller must do
+                that.  """
 
                 m = manifest.Manifest()
                 m.set_fmri(self, fmri)
@@ -473,22 +475,6 @@
 
                 m.store(mpath, ipath)
 
-                if filtered:
-                        filters = []
-                        try:
-                                f = file("%s/filters" % fmri_dir_path, "r")
-                        except IOError, e:
-                                if e.errno != errno.ENOENT:
-                                        raise
-                        else:
-                                filters = [
-                                    (l.strip(), compile(
-                                        l.strip(), "<filter string>", "eval"))
-                                    for l in f.readlines()
-                                ]
-
-                        m.filter(filters)
-
                 return m
 
         def _valid_manifest(self, fmri, manifest):
@@ -509,7 +495,7 @@
 
         def get_manifest(self, fmri, filtered = False):
                 """Find on-disk manifest and create in-memory Manifest
-                object."""
+                object, applying appropriate filters as needed."""
 
                 m = manifest.Manifest()
 
@@ -519,7 +505,7 @@
 
                 # If the manifest isn't there, download.
                 if not os.path.exists(mpath):
-                        m = self._fetch_manifest(fmri, filtered)
+                        m = self._fetch_manifest(fmri)
                 else:
                         mcontent = file(mpath).read()
                         m.set_fmri(self, fmri)
@@ -529,7 +515,7 @@
                 # no authority is attached to the manifest, download a new one.
                 if not self._valid_manifest(fmri, m):
                         try:
-                                m = self._fetch_manifest(fmri, filtered)
+                                m = self._fetch_manifest(fmri)
                         except NameError:
                                 # In thise case, the client has failed to
                                 # download a new manifest with the same name.
@@ -538,6 +524,22 @@
                                 # we have.  Keep the old manifest and drive on.
                                 pass
 
+		# XXX perhaps all of the below should live in Manifest.filter()?
+                if filtered:
+			filters = []
+			try:
+				f = file("%s/filters" % fmri_dir_path, "r")
+			except IOError, e:
+				if e.errno != errno.ENOENT:
+					raise
+			else:
+				filters = [
+				    (l.strip(), compile(
+					l.strip(), "<filter string>", "eval"))
+				    for l in f.readlines()
+				]
+			m.filter(filters)
+
                 return m
 
         @staticmethod
@@ -852,17 +854,16 @@
                 if not pkg.fmri.is_same_authority(cfmri.authority, pfmri.authority):
                         return False
 
-                # Get the catalog for the correct authority
-                cat = self.get_catalog(cfmri)
-
                 # If the catalog has a rename record that names fmri as a
                 # destination, it's possible that pfmri could be the same pkg by
                 # rename.
-
                 if cfmri.is_same_pkg(pfmri):
                         return True
-                else:
-                        return cat.rename_is_same_pkg(cfmri, pfmri)
+
+                # Get the catalog for the correct authority
+                cat = self.get_catalog(cfmri)
+		return cat.rename_is_same_pkg(cfmri, pfmri)
+
 
         def fmri_is_successor(self, cfmri, pfmri):
                 """Since the catalog keeps track of renames, it's no longer
@@ -1021,19 +1022,30 @@
                                 for p in patterns
                                 if pkg.fmri.fmri_match(x.get_pkg_stem(), p)
                                 and not x in pkgs_known ] )
-                elif all_known:
-                        pkgs_known = [ pf for pf in
-                            sorted(self.gen_known_package_fmris()) ]
                 else:
                         pkgs_known = sorted(self.gen_installed_pkgs())
 
-                if pkgs_known:
-                        counthash = {}
-                        self.get_matching_fmris(pkgs_known,
-                                                counthash = counthash)
+		counthash = {}
+		if pkgs_known:
+			#
+			# Walk the installed packages looking for those
+			# which have upgrades available.
+			#
+			self.get_matching_fmris(pkgs_known,
+			    counthash = counthash)
+
+		#
+		# If needed, merge in the rest of the known packages; we don't	
+		# compute upgradability for those, since it's very expensive.
+		#
+		if all_known and not patterns:
+                        pkgs_all_known = [ pf for pf in
+                            self.gen_known_package_fmris() ]
+			pkgs_known += pkgs_all_known
+			pkgs_known = sorted(set(pkgs_known))
 
                 for p in pkgs_known:
-                        if counthash[p] > 1:
+                        if counthash.get(p, 0) > 1:
                                 upgradable = True
                         else:
                                 upgradable = False
@@ -1165,12 +1177,13 @@
                                 out.add(p)
                                 p = os.path.dirname(p)
                 return out
-                        
-                
-        def list_install(self, pkg_list, progress, filters = [],
+
+
+        def make_install_plan(self, pkg_list, progress, filters = [],
             verbose = False, noexecute = False):
                 """Take a list of packages, specified in pkg_list, and attempt
-                to install them on the system.
+                to assemble an appropriate image plan.  This is a helper
+		routine for some common operations in the client.
 
                 This method checks all authorities for a package match;
                 however, it defaults to choosing the preferred authority
@@ -1245,10 +1258,7 @@
 
                 if verbose:
                         print _("After evaluation:")
-                        ip.display()
-
-                if not noexecute:
-                        ip.execute()
+                        print ip.display()
 
 if __name__ == "__main__":
         pass
--- a/src/modules/client/imageplan.py	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/client/imageplan.py	Wed Apr 16 14:39:01 2008 -0700
@@ -355,6 +355,11 @@
 
                 self.state = EVALUATED_OK
                 
+        def nothingtodo(self):
+		""" Test whether this image plan contains any work to do """
+
+		return not self.pkg_plans
+
         def execute(self):
                 """Invoke the evaluated image plan
                 preexecute, execute and postexecute
@@ -363,6 +368,10 @@
                 
                 assert self.state == EVALUATED_OK
 
+		if self.nothingtodo():
+			self.state = EXECUTED_OK
+			return
+
                 npkgs = 0
                 nfiles = 0
                 nbytes = 0
--- a/src/modules/fmri.py	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/modules/fmri.py	Wed Apr 16 14:39:01 2008 -0700
@@ -35,6 +35,13 @@
 # the FMRI.  PREF_AUTH_PFX => preferred authority prefix.
 PREF_AUTH_PFX = "_PRE"
 
+#
+# For is_same_authority(), we need a version of this constant with the
+# trailing _ attached.
+#
+PREF_AUTH_PFX_ = PREF_AUTH_PFX + "_"
+
+
 class PkgFmri(object):
         """The authority is the anchor of a package namespace.  Clients can
         choose to take packages from multiple authorities, and specify a default
@@ -333,37 +340,26 @@
 
 def is_same_authority(auth1, auth2):
         """Compare two authorities.  Return true if they are the same, false
-        otherwise."""
+           otherwise. """
+	#
+	# This code is performance sensitive.  Ensure that you benchmark
+	# changes to it.
+	#
 
-        # Cope with authorities that are None
-        if not auth1 and not auth2:
-                return True
-        elif not auth1 or not auth2:
-                return False
+	# Fastest path for most common case.
+	if auth1 == auth2:
+		return True
 
-        matchstr = "%s_" % PREF_AUTH_PFX
+	if auth1 == None:
+		auth1 = ""
+	if auth2 == None:
+		auth2 = ""
 
-        # Check if the authorities are preferred.  If they are, match.
-        
-        r1 = auth1.startswith(matchstr)
-        r2 = auth2.startswith(matchstr) 
-        
-        if r1 and r2:
+	# String concatenation and string equality are both pretty fast.
+	if ((PREF_AUTH_PFX_ + auth1) == auth2) or \
+	    (auth1 == (PREF_AUTH_PFX_ + auth2)):
+		return True
+        if auth1.startswith(PREF_AUTH_PFX_) and auth2.startswith(PREF_AUTH_PFX_):
                 return True
-       
-        # extract the authority suffix
-        if r1:
-                a1 = auth1[len(matchstr):]
-        else:
-                a1 = auth1
+	return False
 
-        if r2:
-                a2 = auth2[len(matchstr):]
-        else:
-                a2 = auth2
-
-        # Do authorities match?
-        if a1 == a2:
-                return True
-
-        return False
--- a/src/tests/cli/t_upgrade.py	Wed Apr 16 14:26:36 2008 -0700
+++ b/src/tests/cli/t_upgrade.py	Wed Apr 16 14:39:01 2008 -0700
@@ -46,14 +46,14 @@
             close
         """
 
-	incorpA = """
+        incorpA = """
             open [email protected],5.11-0
             add depend type=incorporate fmri=pkg:/[email protected]
             add depend type=incorporate fmri=pkg:/[email protected]
             close
         """
 
-	incorpB =  """
+        incorpB =  """
             open [email protected],5.11-0
             add depend type=incorporate fmri=pkg:/[email protected]
             add depend type=incorporate fmri=pkg:/[email protected]
@@ -115,7 +115,7 @@
             add file /tmp/bronze2 mode=0444 owner=root group=bin path=/etc/amber2
             add license /tmp/copyright3 license=copyright
             add file /tmp/bronzeA2 mode=0444 owner=root group=bin path=/A1/B2/C3/D4/E5/F6/bronzeA2
-	    add depend fmri=pkg:/[email protected] type=require
+            add depend fmri=pkg:/[email protected] type=require
             close 
         """
 
@@ -156,7 +156,17 @@
                 self.pkgsend_bulk(durl, self.amber10)
                 self.pkgsend_bulk(durl, self.bronze10)
 
-                # Now send 2.0 versions of packages.
+                self.image_create(durl)
+                self.pkg("install [email protected]")
+                self.pkg("install bronze")
+
+                self.pkg("list [email protected]")
+                self.pkg("list [email protected]")
+                self.pkg("verify -v")
+
+                #
+                # Now send 2.0 versions of packages.  image-update will (should)
+                # implicitly refresh.
                 #
                 # In version 2.0, several things happen:
                 #
@@ -171,15 +181,6 @@
                 self.pkgsend_bulk(durl, self.amber20)
                 self.pkgsend_bulk(durl, self.bronze20)
 
-
-                self.image_create(durl)
-                self.pkg("install [email protected]")
-                self.pkg("install bronze")
-
-                self.pkg("list [email protected]")
-                self.pkg("list [email protected]")
-                self.pkg("verify -v")
-
                 # Now image-update to get new versions of amber and bronze
                 self.pkg("image-update")
 
@@ -198,16 +199,15 @@
                 # make sure all directories are gone save /var in test image
                 self.assert_(os.listdir(self.get_img_path()) ==  ["var"])
 
-	def test_upgrade2(self):
-		
+        def test_upgrade2(self):
+                """ Basic test for incorporations """
 
                 # Send all pkgs
-
                 durl = self.dc.get_depot_url()
-		self.pkgsend_bulk(durl, self.incorpA)
+                self.pkgsend_bulk(durl, self.incorpA)
                 self.pkgsend_bulk(durl, self.amber10)
                 self.pkgsend_bulk(durl, self.bronze10)
-		self.pkgsend_bulk(durl, self.incorpB)
+                self.pkgsend_bulk(durl, self.incorpB)
                 self.pkgsend_bulk(durl, self.amber20)
                 self.pkgsend_bulk(durl, self.bronze20)
 
@@ -215,8 +215,8 @@
                 self.pkg("install incorpA")
                 self.pkg("install incorpB")
                 self.pkg("install bronze")
-		self.pkg("list [email protected]")
+                self.pkg("list [email protected]")
                 self.pkg("verify -v")
-		
+                
 if __name__ == "__main__":
         unittest.main()