]> git.proxmox.com Git - mirror_lxc.git/blame - src/lxc/lxc-ls
python3: Export get_global_config_item
[mirror_lxc.git] / src / lxc / lxc-ls
CommitLineData
4e7186c5
SG
1#!/usr/bin/python3
2#
3# lxc-ls: List containers
4#
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)
7#
8# (C) Copyright Canonical Ltd. 2012
9#
10# Authors:
11# Stéphane Graber <stgraber@ubuntu.com>
12#
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.
17#
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.
22#
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
250b1eec 25# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
4e7186c5
SG
26#
27
4e7186c5
SG
28import argparse
29import gettext
0e21ea4b 30import json
4e7186c5 31import lxc
daf04e4c 32import os
4e7186c5 33import re
1563f8ef 34import tempfile
4e7186c5
SG
35import sys
36
37_ = gettext.gettext
38gettext.textdomain("lxc-ls")
39
40
41# Functions used later on
42def batch(iterable, cols=1):
43 import math
44
45 length = len(iterable)
46 lines = math.ceil(length / cols)
47
48 for line in range(lines):
49 fields = []
50 for col in range(cols):
51 index = line + (col * lines)
52 if index < length:
53 fields.append(iterable[index])
54 yield fields
55
56
57def getTerminalSize():
58 import os
59 env = os.environ
60
61 def ioctl_GWINSZ(fd):
62 try:
63 import fcntl
64 import termios
65 import struct
66 cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
67 '1234'))
68 return cr
69 except:
70 return
71
72 cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
73 if not cr:
74 try:
75 fd = os.open(os.ctermid(), os.O_RDONLY)
76 cr = ioctl_GWINSZ(fd)
77 os.close(fd)
78 except:
79 pass
80
81 if not cr:
82 cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
83
84 return int(cr[1]), int(cr[0])
85
0e21ea4b 86
cfd149a6 87def getSubContainers(container):
0e21ea4b 88 with open(os.devnull, "w") as fd:
1563f8ef
SG
89 fdnum, path = tempfile.mkstemp()
90 os.remove(path)
91
92 fd = os.fdopen(fdnum)
93
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],
100 stdout=fd)
101
102 fd.seek(0)
103 out = fd.read()
104 fd.close()
0e21ea4b
SG
105 if out:
106 return json.loads(out)
107 return None
108
7f8c4031 109# Constants
63d4950f
SG
110FIELDS = ("name", "state", "ipv4", "ipv6", "autostart", "pid",
111 "memory", "ram", "swap")
0e21ea4b 112
4e7186c5
SG
113# Begin parsing the command line
114parser = argparse.ArgumentParser(description=_("LXC: List containers"),
115 formatter_class=argparse.RawTextHelpFormatter)
116
117parser.add_argument("-1", dest="one", action="store_true",
118 help=_("list one container per line (default when piped)"))
119
9157421a 120parser.add_argument("-P", "--lxcpath", dest="lxcpath", metavar="PATH",
0e21ea4b
SG
121 help=_("Use specified container path"),
122 default=lxc.default_config_path)
9157421a 123
4e7186c5 124parser.add_argument("--active", action="store_true",
3eb967f0 125 help=_("list only active containers"))
4e7186c5
SG
126
127parser.add_argument("--frozen", dest="state", action="append_const",
128 const="FROZEN", help=_("list only frozen containers"))
129
130parser.add_argument("--running", dest="state", action="append_const",
131 const="RUNNING", help=_("list only running containers"))
132
133parser.add_argument("--stopped", dest="state", action="append_const",
134 const="STOPPED", help=_("list only stopped containers"))
135
c5afb6e4 136parser.add_argument("-f", "--fancy", action="store_true",
4e7186c5
SG
137 help=_("use fancy output"))
138
c5afb6e4 139parser.add_argument("-F", "--fancy-format", type=str,
7f8c4031 140 default="name,state,ipv4,ipv6,autostart",
4e7186c5
SG
141 help=_("comma separated list of fields to show"))
142
0e21ea4b
SG
143parser.add_argument("--nesting", dest="nesting", action="store_true",
144 help=_("show nested containers"))
145
4e7186c5
SG
146parser.add_argument("filter", metavar='FILTER', type=str, nargs="?",
147 help=_("regexp to be applied on the container list"))
148
149args = parser.parse_args()
150
151# --active is the same as --running --frozen
152if args.active:
153 if not args.state:
154 args.state = []
3eb967f0 155 args.state += ["RUNNING", "FROZEN", "UNKNOWN"]
4e7186c5
SG
156
157# If the output is piped, default to --one
158if not sys.stdout.isatty():
159 args.one = True
160
0e21ea4b 161# Set the lookup path for the containers
cfd149a6
SG
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
164nest_lxcpath = os.environ.get('NESTED', args.lxcpath)
0e21ea4b 165
4e7186c5
SG
166# Turn args.fancy_format into a list
167args.fancy_format = args.fancy_format.strip().split(",")
168
7f8c4031
SG
169if set(args.fancy_format) - set(FIELDS):
170 parser.error(_("Invalid field(s): %s" %
171 ", ".join(list(set(args.fancy_format) - set(FIELDS)))))
172
0749e740 173# Basic checks
3eb967f0
SG
174## Check for setns
175SUPPORT_SETNS = os.path.exists("/proc/self/ns")
176SUPPORT_SETNS_NET = False
177SUPPORT_SETNS_PID = False
178if SUPPORT_SETNS:
179 SUPPORT_SETNS_NET = os.path.exists("/proc/self/ns/net")
180 SUPPORT_SETNS_PID = os.path.exists("/proc/self/ns/pid")
0749e740 181
9c073d6b
SG
182## Nesting requires setns to pid and net ns
183if args.nesting:
3eb967f0 184 if not SUPPORT_SETNS:
9c073d6b
SG
185 parser.error(_("Showing nested containers requires setns support "
186 "which your kernel doesn't support."))
187
3eb967f0 188 if not SUPPORT_SETNS_NET:
9c073d6b 189 parser.error(_("Showing nested containers requires setns to the "
3eb967f0 190 "network namespace which your kernel doesn't support."))
9c073d6b 191
3eb967f0 192 if not SUPPORT_SETNS_PID:
9c073d6b 193 parser.error(_("Showing nested containers requires setns to the "
3eb967f0 194 "PID namespace which your kernel doesn't support."))
9c073d6b 195
4e7186c5
SG
196# List of containers, stored as dictionaries
197containers = []
cfd149a6 198for container_name in lxc.list_containers(config_path=nest_lxcpath):
0749e740
SG
199 entry = {}
200 entry['name'] = container_name
4e7186c5
SG
201
202 # Apply filter
0749e740 203 if args.filter and not re.match(args.filter, container_name):
4e7186c5
SG
204 continue
205
0749e740 206 # Return before grabbing the object (non-root)
0e21ea4b 207 if not args.state and not args.fancy and not args.nesting:
0749e740
SG
208 containers.append(entry)
209 continue
210
9157421a 211 container = lxc.Container(container_name, args.lxcpath)
0749e740 212
cfd149a6
SG
213 if 'NESTED' in os.environ:
214 container.load_config(os.path.join(nest_lxcpath, container_name,
215 "config"))
216
3eb967f0
SG
217 if container.controllable:
218 state = container.state
219 else:
220 state = 'UNKNOWN'
221
0749e740 222 # Filter by status
3eb967f0 223 if args.state and state not in args.state:
0749e740 224 continue
4e7186c5
SG
225
226 # Nothing more is needed if we're not printing some fancy output
0e21ea4b 227 if not args.fancy and not args.nesting:
4e7186c5
SG
228 containers.append(entry)
229 continue
230
231 # Some extra field we may want
0e21ea4b 232 if 'state' in args.fancy_format or args.nesting:
3eb967f0 233 entry['state'] = state
0e21ea4b
SG
234
235 if 'pid' in args.fancy_format or args.nesting:
4e7186c5 236 entry['pid'] = "-"
3eb967f0
SG
237 if state == 'UNKNOWN':
238 entry['pid'] = state
239 elif container.init_pid != -1:
4e7186c5
SG
240 entry['pid'] = str(container.init_pid)
241
7f8c4031
SG
242 if 'autostart' in args.fancy_format or args.nesting:
243 entry['autostart'] = "NO"
244 try:
245 if container.get_config_item("lxc.start.auto") == "1":
246 entry['autostart'] = "YES"
247
248 groups = container.get_config_item("lxc.group")
249 if len(groups) > 0:
250 entry['autostart'] = "YES (%s)" % ", ".join(groups)
251 except KeyError:
252 pass
253
63d4950f
SG
254 if 'memory' in args.fancy_format or \
255 'ram' in args.fancy_format or \
256 'swap' in args.fancy_format:
257
258 if container.running:
259 try:
260 memory_total = int(container.get_cgroup_item(
261 "memory.usage_in_bytes"))
262 except:
263 memory_total = 0
264
265 try:
266 memory_swap = int(container.get_cgroup_item(
267 "memory.memsw.usage_in_bytes"))
268 except:
269 memory_swap = 0
270 else:
271 memory_total = 0
272 memory_swap = 0
273
274 if 'memory' in args.fancy_format:
275 if container.running:
276 entry['memory'] = "%sMB" % round(memory_total / 1048576, 2)
277 else:
278 entry['memory'] = "-"
279
280 if 'ram' in args.fancy_format:
281 if container.running:
282 entry['ram'] = "%sMB" % round(
283 (memory_total - memory_swap) / 1048576, 2)
284 else:
285 entry['ram'] = "-"
286
287 if 'swap' in args.fancy_format:
288 if container.running:
289 entry['swap'] = "%sMB" % round(memory_swap / 1048576, 2)
290 else:
291 entry['swap'] = "-"
292
4e7186c5 293 # Get the IPs
ad5f1515 294 for family, protocol in {'inet': 'ipv4', 'inet6': 'ipv6'}.items():
0e21ea4b 295 if protocol in args.fancy_format or args.nesting:
4e7186c5 296 entry[protocol] = "-"
3eb967f0
SG
297
298 if state == 'UNKNOWN':
299 entry[protocol] = state
300 continue
301
c868b261 302 if container.running:
ae22a220 303 if not SUPPORT_SETNS_NET:
c868b261
ÇO
304 entry[protocol] = 'UNKNOWN'
305 continue
306
307 ips = container.get_ips(family=family)
308 if ips:
309 entry[protocol] = ", ".join(ips)
4e7186c5 310
0e21ea4b 311 # Append the container
4e7186c5
SG
312 containers.append(entry)
313
0e21ea4b 314 # Nested containers
1563f8ef 315 if args.nesting and container.state == "RUNNING":
cfd149a6 316 sub = getSubContainers(container)
0e21ea4b
SG
317 if sub:
318 for entry in sub:
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',
323 entry['name'])
324 entry['name'] = "%s/%s" % (container_name, entry['name'])
325 containers += sub
326
327# Deal with json output:
328if 'NESTED' in os.environ:
329 print(json.dumps(containers))
330 sys.exit(0)
4e7186c5
SG
331
332# Print the list
333## Standard list with one entry per line
334if not args.fancy and args.one:
335 for container in sorted(containers,
336 key=lambda container: container['name']):
337 print(container['name'])
338 sys.exit(0)
339
340## Standard list with multiple entries per line
341if not args.fancy and not args.one:
342 # Get the longest name and extra simple list
343 field_maxlength = 0
344 container_names = []
345 for container in containers:
346 if len(container['name']) > field_maxlength:
347 field_maxlength = len(container['name'])
348 container_names.append(container['name'])
349
350 # Figure out how many we can put per line
351 width = getTerminalSize()[0]
352
353 entries = int(width / (field_maxlength + 2))
354 if entries == 0:
355 entries = 1
356
357 for line in batch(sorted(container_names), entries):
358 line_format = ""
359 for index in range(len(line)):
360 line_format += "{line[%s]:%s}" % (index, field_maxlength + 2)
361
362 print(line_format.format(line=line))
363
364## Fancy listing
365if args.fancy:
366 field_maxlength = {}
367
368 # Get the maximum length per field
369 for field in args.fancy_format:
370 field_maxlength[field] = len(field)
371
372 for container in containers:
373 for field in args.fancy_format:
0e21ea4b
SG
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]:
4e7186c5
SG
380 field_maxlength[field] = len(container[field])
381
382 # Generate the line format string based on the maximum length and
383 # a 2 character padding
384 line_format = ""
385 index = 0
386 for field in args.fancy_format:
387 line_format += "{fields[%s]:%s}" % (index, field_maxlength[field] + 2)
388 index += 1
389
390 # Get the line length minus the padding of the last field
391 line_length = -2
392 for field in field_maxlength:
393 line_length += field_maxlength[field] + 2
394
395 # Print header
396 print(line_format.format(fields=[header.upper()
397 for header in args.fancy_format]))
398 print("-" * line_length)
399
400 # Print the entries
401 for container in sorted(containers,
402 key=lambda container: container['name']):
0e21ea4b
SG
403 fields = []
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'])
408 else:
409 fields.append(container[field])
410
4e7186c5 411 print(line_format.format(fields=fields))