[lxc-devel] [PATCH] Rewrite lxc-ls in python

Stéphane Graber stgraber at ubuntu.com
Wed Nov 21 23:04:03 UTC 2012


This rewrite is mostly compatible with the shell version.
--active and -1 still work and behave as they used to.

This adds --running, --stopped and --frozen as state filters.

A new "fancy" view is also implemented (can be used with --fancy) and
will show containers in a column-based interface with the following fields:
 - name
 - state
 - ipv4
 - ipv6
 - pid of init

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 .gitignore          |   1 -
 configure.ac        |   1 -
 doc/lxc-ls.sgml.in  | 150 ++++++++++++++++++++++-----------
 src/lxc/Makefile.am |   2 +-
 src/lxc/lxc-ls      | 236
++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/lxc/lxc-ls.in   |  94 ---------------------
 6 files changed, 340 insertions(+), 144 deletions(-)
 create mode 100644 src/lxc/lxc-ls
 delete mode 100644 src/lxc/lxc-ls.in

diff --git a/.gitignore b/.gitignore
index d5b07e3..398e4fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,7 +50,6 @@ src/lxc/lxc-freeze
 src/lxc/lxc-info
 src/lxc/lxc-init
 src/lxc/lxc-kill
-src/lxc/lxc-ls
 src/lxc/lxc-monitor
 src/lxc/lxc-netstat
 src/lxc/lxc-ps
diff --git a/configure.ac b/configure.ac
index b6fa365..5f0cc0b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -257,7 +257,6 @@ AC_CONFIG_FILES([
 	src/Makefile
 	src/lxc/Makefile
 	src/lxc/lxc-ps
-	src/lxc/lxc-ls
 	src/lxc/lxc-netstat
 	src/lxc/lxc-checkconfig
 	src/lxc/lxc-setcap
diff --git a/doc/lxc-ls.sgml.in b/doc/lxc-ls.sgml.in
index f5f6573..138908d 100644
--- a/doc/lxc-ls.sgml.in
+++ b/doc/lxc-ls.sgml.in
@@ -49,8 +49,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston,
MA 02111-1307 USA
   <refsynopsisdiv>
     <cmdsynopsis>
       <command>lxc-ls</command>
+      <arg choice="opt">-1</arg>
       <arg choice="opt">--active</arg>
-      <arg choice="opt">ls option</arg>
+      <arg choice="opt">--frozen</arg>
+      <arg choice="opt">--running</arg>
+      <arg choice="opt">--stopped</arg>
+      <arg choice="opt">--fancy</arg>
+      <arg choice="opt">--fancy-format</arg>
+      <arg choice="opt">filter</arg>
     </cmdsynopsis>
   </refsynopsisdiv>
 @@ -65,77 +71,127 @@ Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
   <refsect1>
     <title>Options</title>
     <variablelist>
+      <varlistentry>
+        <term>
+          <option><optional>-1</optional></option>
+        </term>
+        <listitem>
+          <para>
+            Show one entry per line. (default when /dev/stdout isn't a tty)
+          </para>
+        </listitem>
+      </varlistentry>
        <varlistentry>
-	<term>
-	  <option><optional>--active</optional></option>
-	</term>
-	<listitem>
-	  <para>
-	    List active containers.
-	  </para>
-	</listitem>
+        <term>
+          <option><optional>--active</optional></option>
+        </term>
+        <listitem>
+          <para>
+            List only active containers (same as --frozen --running).
+          </para>
+        </listitem>
       </varlistentry>
        <varlistentry>
-	<term>
-	  <option><optional>ls options</optional></option>
-	</term>
-	<listitem>
-	  <para>
-	    The option passed to <command>lxc-ls</command> are the
-	    same as the <command>ls</command> command.
-	  </para>
-	</listitem>
+        <term>
+          <option><optional>--frozen</optional></option>
+        </term>
+        <listitem>
+          <para>
+            List only frozen containers.
+          </para>
+        </listitem>
       </varlistentry>
 -    </variablelist>
+      <varlistentry>
+        <term>
+          <option><optional>--running</optional></option>
+        </term>
+        <listitem>
+          <para>
+            List only running containers.
+          </para>
+        </listitem>
+      </varlistentry>
 -  </refsect1>
+      <varlistentry>
+        <term>
+          <option><optional>--stopped</optional></option>
+        </term>
+        <listitem>
+          <para>
+            List only stopped containers.
+          </para>
+        </listitem>
+      </varlistentry>
 -  <refsect1>
-    <title>Examples</title>
-    <variablelist>
       <varlistentry>
-	<term>lxc-ls -l</term>
-	<listitem>
-	<para>
-	  list all the container and their permissions.
-	</para>
-	</listitem>
+        <term>
+          <option><optional>--fancy</optional></option>
+        </term>
+        <listitem>
+          <para>
+            Use a fancy, column-based output.
+          </para>
+        </listitem>
       </varlistentry>
        <varlistentry>
-	<term>lxc-ls --active -1</term>
-	<listitem>
-	<para>
-	  list active containers and display the list in one column.
-	</para>
-	</listitem>
+        <term>
+          <option><optional>--fancy-format</optional></option>
+        </term>
+        <listitem>
+          <para>
+            Comma separate list of column to show in the fancy output.
+            Valid values are: name, state, ipv4, ipv6 and pid
+            Default is: name,state,ipv4,ipv6
+          </para>
+        </listitem>
       </varlistentry>
 +      <varlistentry>
+        <term>
+          <option><optional>filter</optional></option>
+        </term>
+        <listitem>
+          <para>
+            The filter passed to <command>lxc-ls</command> will be
+            applied to the container name. The format is a regular
expression.
+          </para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
    <refsect1>
-    <title>See Also</title>
-
-    <simpara>    -      <citerefentry>
-	<refentrytitle>ls</refentrytitle>
-	<manvolnum>1</manvolnum>
-      </citerefentry>,
-    </simpara>
+    <title>Examples</title>
+    <variablelist>
+      <varlistentry>
+        <term>lxc-ls --fancy</term>
+        <listitem>
+        <para>
+          list all the containers, listing one per line along with its
+          name, state, ipv4 and ipv6 addresses.
+        </para>
+        </listitem>
+      </varlistentry>
 +      <varlistentry>
+        <term>lxc-ls --active -1</term>
+        <listitem>
+        <para>
+          list active containers and display the list in one column.
+        </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
   </refsect1>
 -  &seealso;
-
   <refsect1>
     <title>Author</title>
-    <para>Daniel Lezcano <email>daniel.lezcano at free.fr</email></para>
+    <para>Stéphane Graber <email>stgraber at ubuntu.com</email></para>
   </refsect1>
-
 </refentry>
  <!-- Keep this comment at the end of the file
diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am
index 7d86ad6..9219ba6 100644
--- a/src/lxc/Makefile.am
+++ b/src/lxc/Makefile.am
@@ -87,7 +87,6 @@ liblxc_so_LDADD = -lutil $(CAP_LIBS) $(APPARMOR_LIBS)
$(SECCOMP_LIBS) -lrt
 bin_SCRIPTS = \
 	lxc-ps \
 	lxc-netstat \
-	lxc-ls \
 	lxc-checkconfig \
 	lxc-setcap \
 	lxc-setuid \
@@ -98,6 +97,7 @@ bin_SCRIPTS = \
 	lxc-destroy
  if ENABLE_PYTHON
+    bin_SCRIPTS += lxc-ls
     bin_SCRIPTS += lxc-start-ephemeral
 endif
 diff --git a/src/lxc/lxc-ls b/src/lxc/lxc-ls
new file mode 100644
index 0000000..8a1d1ed
--- /dev/null
+++ b/src/lxc/lxc-ls
@@ -0,0 +1,236 @@
+#!/usr/bin/python3
+#
+# lxc-ls: List containers
+#
+# This python implementation is based on the work done in the original
+# shell implementation done by Serge Hallyn in Ubuntu (and other
contributors)
+#
+# (C) Copyright Canonical Ltd. 2012
+#
+# Authors:
+# Stéphane Graber <stgraber at ubuntu.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+# NOTE: To remove once the API is stabilized
+import warnings
+warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable")
+
+import argparse
+import gettext
+import lxc
+import re
+import sys
+
+_ = gettext.gettext
+gettext.textdomain("lxc-ls")
+
+
+# Functions used later on
+def batch(iterable, cols=1):
+    import math
+
+    length = len(iterable)
+    lines = math.ceil(length / cols)
+
+    for line in range(lines):
+        fields = []
+        for col in range(cols):
+            index = line + (col * lines)
+            if index < length:
+                fields.append(iterable[index])
+        yield fields
+
+
+def getTerminalSize():
+    import os
+    env = os.environ
+
+    def ioctl_GWINSZ(fd):
+        try:
+            import fcntl
+            import termios
+            import struct
+            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
+                               '1234'))
+            return cr
+        except:
+            return
+
+    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
+    if not cr:
+        try:
+            fd = os.open(os.ctermid(), os.O_RDONLY)
+            cr = ioctl_GWINSZ(fd)
+            os.close(fd)
+        except:
+            pass
+
+    if not cr:
+        cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
+
+    return int(cr[1]), int(cr[0])
+
+# Begin parsing the command line
+parser = argparse.ArgumentParser(description=_("LXC: List containers"),
+
formatter_class=argparse.RawTextHelpFormatter)
+
+parser.add_argument("-1", dest="one", action="store_true",
+                    help=_("list one container per line (default when
piped)"))
+
+parser.add_argument("--active", action="store_true",
+                    help=_("list only active containers "
+                           "(same as --running --frozen)"))
+
+parser.add_argument("--frozen", dest="state", action="append_const",
+                    const="FROZEN", help=_("list only frozen containers"))
+
+parser.add_argument("--running", dest="state", action="append_const",
+                    const="RUNNING", help=_("list only running
containers"))
+
+parser.add_argument("--stopped", dest="state", action="append_const",
+                    const="STOPPED", help=_("list only stopped
containers"))
+
+parser.add_argument("--fancy", action="store_true",
+                    help=_("use fancy output"))
+
+parser.add_argument("--fancy-format", type=str,
default="name,state,ipv4,ipv6",
+                    help=_("comma separated list of fields to show"))
+
+parser.add_argument("filter", metavar='FILTER', type=str, nargs="?",
+                    help=_("regexp to be applied on the container list"))
+
+args = parser.parse_args()
+
+# --active is the same as --running --frozen
+if args.active:
+    if not args.state:
+        args.state = []
+    args.state += ["RUNNING", "FROZEN"]
+
+# If the output is piped, default to --one
+if not sys.stdout.isatty():
+    args.one = True
+
+# Turn args.fancy_format into a list
+args.fancy_format = args.fancy_format.strip().split(",")
+
+# List of containers, stored as dictionaries
+containers = []
+for container in lxc.list_containers(as_object=True):
+    # Filter by status
+    if args.state and container.state not in args.state:
+        continue
+
+    # Apply filter
+    if args.filter and not re.match(args.filter, container.name):
+        continue
+
+    entry = {}
+    entry['name'] = container.name
+
+    # Nothing more is needed if we're not printing some fancy output
+    if not args.fancy:
+        containers.append(entry)
+        continue
+
+    # Some extra field we may want
+    if 'state' in args.fancy_format:
+        entry['state'] = container.state
+    if 'pid' in args.fancy_format:
+        entry['pid'] = "-"
+        if container.init_pid != -1:
+            entry['pid'] = str(container.init_pid)
+
+    # Get the IPs
+    for protocol in ('ipv4', 'ipv6'):
+        if protocol in args.fancy_format:
+            entry[protocol] = "-"
+            ips = container.get_ips(protocol=protocol, timeout=1)
+            if ips:
+                entry[protocol] = ", ".join(ips)
+
+    containers.append(entry)
+
+
+# Print the list
+## Standard list with one entry per line
+if not args.fancy and args.one:
+    for container in sorted(containers,
+                            key=lambda container: container['name']):
+        print(container['name'])
+    sys.exit(0)
+
+## Standard list with multiple entries per line
+if not args.fancy and not args.one:
+    # Get the longest name and extra simple list
+    field_maxlength = 0
+    container_names = []
+    for container in containers:
+        if len(container['name']) > field_maxlength:
+            field_maxlength = len(container['name'])
+        container_names.append(container['name'])
+
+    # Figure out how many we can put per line
+    width = getTerminalSize()[0]
+
+    entries = int(width / (field_maxlength + 2))
+    if entries == 0:
+        entries = 1
+
+    for line in batch(sorted(container_names), entries):
+        line_format = ""
+        for index in range(len(line)):
+            line_format += "{line[%s]:%s}" % (index, field_maxlength + 2)
+
+        print(line_format.format(line=line))
+
+## Fancy listing
+if args.fancy:
+    field_maxlength = {}
+
+    # Get the maximum length per field
+    for field in args.fancy_format:
+        field_maxlength[field] = len(field)
+
+    for container in containers:
+        for field in args.fancy_format:
+            if len(container[field]) > field_maxlength[field]:
+                field_maxlength[field] = len(container[field])
+
+    # Generate the line format string based on the maximum length and
+    # a 2 character padding
+    line_format = ""
+    index = 0
+    for field in args.fancy_format:
+        line_format += "{fields[%s]:%s}" % (index,
field_maxlength[field] + 2)
+        index += 1
+
+    # Get the line length minus the padding of the last field
+    line_length = -2
+    for field in field_maxlength:
+        line_length += field_maxlength[field] + 2
+
+    # Print header
+    print(line_format.format(fields=[header.upper()
+                                     for header in args.fancy_format]))
+    print("-" * line_length)
+
+    # Print the entries
+    for container in sorted(containers,
+                            key=lambda container: container['name']):
+        fields = [container[field] for field in args.fancy_format]
+        print(line_format.format(fields=fields))
diff --git a/src/lxc/lxc-ls.in b/src/lxc/lxc-ls.in
deleted file mode 100644
index f26572d..0000000
--- a/src/lxc/lxc-ls.in
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/bin/bash
-
-#
-# lxc: linux Container library
-
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-lxc_path=@LXCPATH@
-
-usage()
-{
-	echo "usage: $(basename $0) [--active] [--] [LS_OPTIONS...]" >&2
-}
-
-help() {
-	usage
-	echo >&2
-	echo "List containers existing on the system." >&2
-	echo >&2
-	echo "  --active     list active containers" >&2
-	echo "  LS_OPTIONS   ls command options (see \`ls --help')" >&2
-}
-
-get_parent_cgroup()
-{
-	local hierarchies hierarchy fields subsystems init_cgroup mountpoint
-
-	parent_cgroup=""
-
-	# Obtain a list of hierarchies that contain one or more subsystems
-	hierarchies=$(tail -n +2 /proc/cgroups | cut -f 2)
-
-	# Iterate through the list until a suitable hierarchy is found
-	for hierarchy in $hierarchies; do
-		# Obtain information about the init process in the hierarchy
-		fields=$(grep -E "^$hierarchy:" /proc/1/cgroup | head -n 1)
-		if [ -z "$fields" ]; then continue; fi
-		fields=${fields#*:}
-
-		# Get a comma-separated list of the hierarchy's subsystems
-		subsystems=${fields%:*}
-
-		# Get the cgroup of the init process in the hierarchy
-		init_cgroup=${fields#*:}
-
-		# Get the filesystem mountpoint of the hierarchy
-		mountpoint=$(grep -E "^[^ ]+ [^ ]+ cgroup ([^ ]+,)?$subsystems(,[^
]+)? " /proc/self/mounts | cut -d ' ' -f 2)
-		if [ -z "$mountpoint" ]; then continue; fi
-
-		# Return the absolute path to the containers' parent cgroup
-		# (do not append '/lxc' if the hierarchy contains the 'ns' subsystem)
-		if [[ ",$subsystems," == *,ns,* ]]; then
-			parent_cgroup="${mountpoint}${init_cgroup%/}"
-		else
-			parent_cgroup="${mountpoint}${init_cgroup%/}/lxc"
-		fi
-		break
-	done
-}
-
-directory=$(readlink -f "$lxc_path")
-
-for i in "$@"; do
-	case $i in
-		--help)
-			help; exit;;
-		--active)
-			get_parent_cgroup; directory="$parent_cgroup"; shift;;
-		--)
-			shift; break;;
-		*)
-			break;;
-        esac
-done
-
-containers=""
-if [ ! -z "$directory" ]; then
-	containers=$(find $directory -mindepth 1 -maxdepth 1 -type d -printf
"%f\n" 2>/dev/null)
-fi
-
-cd "$directory"
-ls -d $@ -- $containers
-- 
1.8.0


-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 899 bytes
Desc: OpenPGP digital signature
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20121121/9dc509a7/attachment.pgp>


More information about the lxc-devel mailing list