]> git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/lxc-ls
python3: Export get_global_config_item
[mirror_lxc.git] / src / lxc / lxc-ls
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
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 #
27
28 import argparse
29 import gettext
30 import json
31 import lxc
32 import os
33 import re
34 import tempfile
35 import sys
36
37 _ = gettext.gettext
38 gettext.textdomain("lxc-ls")
39
40
41 # Functions used later on
42 def 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
57 def 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
86
87 def getSubContainers(container):
88 with open(os.devnull, "w") as fd:
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()
105 if out:
106 return json.loads(out)
107 return None
108
109 # Constants
110 FIELDS = ("name", "state", "ipv4", "ipv6", "autostart", "pid",
111 "memory", "ram", "swap")
112
113 # Begin parsing the command line
114 parser = argparse.ArgumentParser(description=_("LXC: List containers"),
115 formatter_class=argparse.RawTextHelpFormatter)
116
117 parser.add_argument("-1", dest="one", action="store_true",
118 help=_("list one container per line (default when piped)"))
119
120 parser.add_argument("-P", "--lxcpath", dest="lxcpath", metavar="PATH",
121 help=_("Use specified container path"),
122 default=lxc.default_config_path)
123
124 parser.add_argument("--active", action="store_true",
125 help=_("list only active containers"))
126
127 parser.add_argument("--frozen", dest="state", action="append_const",
128 const="FROZEN", help=_("list only frozen containers"))
129
130 parser.add_argument("--running", dest="state", action="append_const",
131 const="RUNNING", help=_("list only running containers"))
132
133 parser.add_argument("--stopped", dest="state", action="append_const",
134 const="STOPPED", help=_("list only stopped containers"))
135
136 parser.add_argument("-f", "--fancy", action="store_true",
137 help=_("use fancy output"))
138
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"))
142
143 parser.add_argument("--nesting", dest="nesting", action="store_true",
144 help=_("show nested containers"))
145
146 parser.add_argument("filter", metavar='FILTER', type=str, nargs="?",
147 help=_("regexp to be applied on the container list"))
148
149 args = parser.parse_args()
150
151 # --active is the same as --running --frozen
152 if args.active:
153 if not args.state:
154 args.state = []
155 args.state += ["RUNNING", "FROZEN", "UNKNOWN"]
156
157 # If the output is piped, default to --one
158 if not sys.stdout.isatty():
159 args.one = True
160
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)
165
166 # Turn args.fancy_format into a list
167 args.fancy_format = args.fancy_format.strip().split(",")
168
169 if set(args.fancy_format) - set(FIELDS):
170 parser.error(_("Invalid field(s): %s" %
171 ", ".join(list(set(args.fancy_format) - set(FIELDS)))))
172
173 # Basic checks
174 ## Check for setns
175 SUPPORT_SETNS = os.path.exists("/proc/self/ns")
176 SUPPORT_SETNS_NET = False
177 SUPPORT_SETNS_PID = False
178 if SUPPORT_SETNS:
179 SUPPORT_SETNS_NET = os.path.exists("/proc/self/ns/net")
180 SUPPORT_SETNS_PID = os.path.exists("/proc/self/ns/pid")
181
182 ## Nesting requires setns to pid and net ns
183 if args.nesting:
184 if not SUPPORT_SETNS:
185 parser.error(_("Showing nested containers requires setns support "
186 "which your kernel doesn't support."))
187
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."))
191
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."))
195
196 # List of containers, stored as dictionaries
197 containers = []
198 for container_name in lxc.list_containers(config_path=nest_lxcpath):
199 entry = {}
200 entry['name'] = container_name
201
202 # Apply filter
203 if args.filter and not re.match(args.filter, container_name):
204 continue
205
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)
209 continue
210
211 container = lxc.Container(container_name, args.lxcpath)
212
213 if 'NESTED' in os.environ:
214 container.load_config(os.path.join(nest_lxcpath, container_name,
215 "config"))
216
217 if container.controllable:
218 state = container.state
219 else:
220 state = 'UNKNOWN'
221
222 # Filter by status
223 if args.state and state not in args.state:
224 continue
225
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)
229 continue
230
231 # Some extra field we may want
232 if 'state' in args.fancy_format or args.nesting:
233 entry['state'] = state
234
235 if 'pid' in args.fancy_format or args.nesting:
236 entry['pid'] = "-"
237 if state == 'UNKNOWN':
238 entry['pid'] = state
239 elif container.init_pid != -1:
240 entry['pid'] = str(container.init_pid)
241
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
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
293 # Get the IPs
294 for family, protocol in {'inet': 'ipv4', 'inet6': 'ipv6'}.items():
295 if protocol in args.fancy_format or args.nesting:
296 entry[protocol] = "-"
297
298 if state == 'UNKNOWN':
299 entry[protocol] = state
300 continue
301
302 if container.running:
303 if not SUPPORT_SETNS_NET:
304 entry[protocol] = 'UNKNOWN'
305 continue
306
307 ips = container.get_ips(family=family)
308 if ips:
309 entry[protocol] = ", ".join(ips)
310
311 # Append the container
312 containers.append(entry)
313
314 # Nested containers
315 if args.nesting and container.state == "RUNNING":
316 sub = getSubContainers(container)
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:
328 if 'NESTED' in os.environ:
329 print(json.dumps(containers))
330 sys.exit(0)
331
332 # Print the list
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'])
338 sys.exit(0)
339
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
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
365 if 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:
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])
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']):
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
411 print(line_format.format(fields=fields))