3 # lxc-ls: List containers
5 # This python implementation is based on the work done in the original
6 # shell implementation done by Serge Hallyn in Ubuntu (and other contributors)
8 # (C) Copyright Canonical Ltd. 2012
11 # Stéphane Graber <stgraber@ubuntu.com>
13 # This library is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU Lesser General Public
15 # License as published by the Free Software Foundation; either
16 # version 2.1 of the License, or (at your option) any later version.
18 # This library is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # Lesser General Public License for more details.
23 # You should have received a copy of the GNU Lesser General Public
24 # License along with this library; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
38 gettext
.textdomain("lxc-ls")
41 # Functions used later on
42 def batch(iterable
, cols
=1):
45 length
= len(iterable
)
46 lines
= math
.ceil(length
/ cols
)
48 for line
in range(lines
):
50 for col
in range(cols
):
51 index
= line
+ (col
* lines
)
53 fields
.append(iterable
[index
])
57 def getTerminalSize():
66 cr
= struct
.unpack('hh', fcntl
.ioctl(fd
, termios
.TIOCGWINSZ
,
72 cr
= ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
75 fd
= os
.open(os
.ctermid(), os
.O_RDONLY
)
82 cr
= (env
.get('LINES', 25), env
.get('COLUMNS', 80))
84 return int(cr
[1]), int(cr
[0])
87 def getSubContainers(container
):
88 with
open(os
.devnull
, "w") as fd
:
89 fdnum
, path
= tempfile
.mkstemp()
94 container
.attach_wait(
95 lxc
.attach_run_command
, [sys
.argv
[0], "--nesting"],
96 attach_flags
=(lxc
.LXC_ATTACH_REMOUNT_PROC_SYS
),
97 namespaces
=(lxc
.CLONE_NEWNET
+ lxc
.CLONE_NEWPID
),
98 extra_env_vars
=["NESTED=/proc/1/root/%s" %
99 lxc
.default_config_path
],
106 return json
.loads(out
)
110 FIELDS
= ("name", "state", "ipv4", "ipv6", "autostart", "pid",
111 "memory", "ram", "swap")
113 # Begin parsing the command line
114 parser
= argparse
.ArgumentParser(description
=_("LXC: List containers"),
115 formatter_class
=argparse
.RawTextHelpFormatter
)
117 parser
.add_argument("-1", dest
="one", action
="store_true",
118 help=_("list one container per line (default when piped)"))
120 parser
.add_argument("-P", "--lxcpath", dest
="lxcpath", metavar
="PATH",
121 help=_("Use specified container path"),
122 default
=lxc
.default_config_path
)
124 parser
.add_argument("--active", action
="store_true",
125 help=_("list only active containers"))
127 parser
.add_argument("--frozen", dest
="state", action
="append_const",
128 const
="FROZEN", help=_("list only frozen containers"))
130 parser
.add_argument("--running", dest
="state", action
="append_const",
131 const
="RUNNING", help=_("list only running containers"))
133 parser
.add_argument("--stopped", dest
="state", action
="append_const",
134 const
="STOPPED", help=_("list only stopped containers"))
136 parser
.add_argument("-f", "--fancy", action
="store_true",
137 help=_("use fancy output"))
139 parser
.add_argument("-F", "--fancy-format", type=str,
140 default
="name,state,ipv4,ipv6,autostart",
141 help=_("comma separated list of fields to show"))
143 parser
.add_argument("--nesting", dest
="nesting", action
="store_true",
144 help=_("show nested containers"))
146 parser
.add_argument("filter", metavar
='FILTER', type=str, nargs
="?",
147 help=_("regexp to be applied on the container list"))
149 args
= parser
.parse_args()
151 # --active is the same as --running --frozen
155 args
.state
+= ["RUNNING", "FROZEN", "UNKNOWN"]
157 # If the output is piped, default to --one
158 if not sys
.stdout
.isatty():
161 # Set the lookup path for the containers
162 # This value will contain the full path for a nested containers
163 # use args.lxcpath if you need the value relative to the container
164 nest_lxcpath
= os
.environ
.get('NESTED', args
.lxcpath
)
166 # Turn args.fancy_format into a list
167 args
.fancy_format
= args
.fancy_format
.strip().split(",")
169 if set(args
.fancy_format
) - set(FIELDS
):
170 parser
.error(_("Invalid field(s): %s" %
171 ", ".join(list(set(args
.fancy_format
) - set(FIELDS
)))))
175 SUPPORT_SETNS
= os
.path
.exists("/proc/self/ns")
176 SUPPORT_SETNS_NET
= False
177 SUPPORT_SETNS_PID
= False
179 SUPPORT_SETNS_NET
= os
.path
.exists("/proc/self/ns/net")
180 SUPPORT_SETNS_PID
= os
.path
.exists("/proc/self/ns/pid")
182 ## Nesting requires setns to pid and net ns
184 if not SUPPORT_SETNS
:
185 parser
.error(_("Showing nested containers requires setns support "
186 "which your kernel doesn't support."))
188 if not SUPPORT_SETNS_NET
:
189 parser
.error(_("Showing nested containers requires setns to the "
190 "network namespace which your kernel doesn't support."))
192 if not SUPPORT_SETNS_PID
:
193 parser
.error(_("Showing nested containers requires setns to the "
194 "PID namespace which your kernel doesn't support."))
196 # List of containers, stored as dictionaries
198 for container_name
in lxc
.list_containers(config_path
=nest_lxcpath
):
200 entry
['name'] = container_name
203 if args
.filter and not re
.match(args
.filter, container_name
):
206 # Return before grabbing the object (non-root)
207 if not args
.state
and not args
.fancy
and not args
.nesting
:
208 containers
.append(entry
)
211 container
= lxc
.Container(container_name
, args
.lxcpath
)
213 if 'NESTED' in os
.environ
:
214 container
.load_config(os
.path
.join(nest_lxcpath
, container_name
,
217 if container
.controllable
:
218 state
= container
.state
223 if args
.state
and state
not in args
.state
:
226 # Nothing more is needed if we're not printing some fancy output
227 if not args
.fancy
and not args
.nesting
:
228 containers
.append(entry
)
231 # Some extra field we may want
232 if 'state' in args
.fancy_format
or args
.nesting
:
233 entry
['state'] = state
235 if 'pid' in args
.fancy_format
or args
.nesting
:
237 if state
== 'UNKNOWN':
239 elif container
.init_pid
!= -1:
240 entry
['pid'] = str(container
.init_pid
)
242 if 'autostart' in args
.fancy_format
or args
.nesting
:
243 entry
['autostart'] = "NO"
245 if container
.get_config_item("lxc.start.auto") == "1":
246 entry
['autostart'] = "YES"
248 groups
= container
.get_config_item("lxc.group")
250 entry
['autostart'] = "YES (%s)" % ", ".join(groups
)
254 if 'memory' in args
.fancy_format
or \
255 'ram' in args
.fancy_format
or \
256 'swap' in args
.fancy_format
:
258 if container
.running
:
260 memory_total
= int(container
.get_cgroup_item(
261 "memory.usage_in_bytes"))
266 memory_swap
= int(container
.get_cgroup_item(
267 "memory.memsw.usage_in_bytes"))
274 if 'memory' in args
.fancy_format
:
275 if container
.running
:
276 entry
['memory'] = "%sMB" % round(memory_total
/ 1048576, 2)
278 entry
['memory'] = "-"
280 if 'ram' in args
.fancy_format
:
281 if container
.running
:
282 entry
['ram'] = "%sMB" % round(
283 (memory_total
- memory_swap
) / 1048576, 2)
287 if 'swap' in args
.fancy_format
:
288 if container
.running
:
289 entry
['swap'] = "%sMB" % round(memory_swap
/ 1048576, 2)
294 for family
, protocol
in {'inet': 'ipv4', 'inet6': 'ipv6'}.items():
295 if protocol
in args
.fancy_format
or args
.nesting
:
296 entry
[protocol
] = "-"
298 if state
== 'UNKNOWN':
299 entry
[protocol
] = state
302 if container
.running
:
303 if not SUPPORT_SETNS_NET
:
304 entry
[protocol
] = 'UNKNOWN'
307 ips
= container
.get_ips(family
=family
)
309 entry
[protocol
] = ", ".join(ips
)
311 # Append the container
312 containers
.append(entry
)
315 if args
.nesting
and container
.state
== "RUNNING":
316 sub
= getSubContainers(container
)
319 if 'nesting_parent' not in entry
:
320 entry
['nesting_parent'] = []
321 entry
['nesting_parent'].insert(0, container_name
)
322 entry
['nesting_real_name'] = entry
.get('nesting_real_name',
324 entry
['name'] = "%s/%s" % (container_name
, entry
['name'])
327 # Deal with json output:
328 if 'NESTED' in os
.environ
:
329 print(json
.dumps(containers
))
333 ## Standard list with one entry per line
334 if not args
.fancy
and args
.one
:
335 for container
in sorted(containers
,
336 key
=lambda container
: container
['name']):
337 print(container
['name'])
340 ## Standard list with multiple entries per line
341 if not args
.fancy
and not args
.one
:
342 # Get the longest name and extra simple list
345 for container
in containers
:
346 if len(container
['name']) > field_maxlength
:
347 field_maxlength
= len(container
['name'])
348 container_names
.append(container
['name'])
350 # Figure out how many we can put per line
351 width
= getTerminalSize()[0]
353 entries
= int(width
/ (field_maxlength
+ 2))
357 for line
in batch(sorted(container_names
), entries
):
359 for index
in range(len(line
)):
360 line_format
+= "{line[%s]:%s}" % (index
, field_maxlength
+ 2)
362 print(line_format
.format(line
=line
))
368 # Get the maximum length per field
369 for field
in args
.fancy_format
:
370 field_maxlength
[field
] = len(field
)
372 for container
in containers
:
373 for field
in args
.fancy_format
:
374 if field
== 'name' and 'nesting_real_name' in container
:
375 fieldlen
= len(" " * ((len(container
['nesting_parent']) - 1)
376 * 4) + " \_ " + container
['nesting_real_name'])
377 if fieldlen
> field_maxlength
[field
]:
378 field_maxlength
[field
] = fieldlen
379 elif len(container
[field
]) > field_maxlength
[field
]:
380 field_maxlength
[field
] = len(container
[field
])
382 # Generate the line format string based on the maximum length and
383 # a 2 character padding
386 for field
in args
.fancy_format
:
387 line_format
+= "{fields[%s]:%s}" % (index
, field_maxlength
[field
] + 2)
390 # Get the line length minus the padding of the last field
392 for field
in field_maxlength
:
393 line_length
+= field_maxlength
[field
] + 2
396 print(line_format
.format(fields
=[header
.upper()
397 for header
in args
.fancy_format
]))
398 print("-" * line_length
)
401 for container
in sorted(containers
,
402 key
=lambda container
: container
['name']):
404 for field
in args
.fancy_format
:
405 if field
== 'name' and 'nesting_real_name' in container
:
406 prefix
= " " * ((len(container
['nesting_parent']) - 1) * 4)
407 fields
.append(prefix
+ " \_ " + container
['nesting_real_name'])
409 fields
.append(container
[field
])
411 print(line_format
.format(fields
=fields
))