]> git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/lxc-ls
licensing: Add missing headers and FSF address
[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 # NOTE: To remove once the API is stabilized
29 import warnings
30 warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable")
31
32 import argparse
33 import gettext
34 import json
35 import lxc
36 import os
37 import re
38 import subprocess
39 import sys
40
41 _ = gettext.gettext
42 gettext.textdomain("lxc-ls")
43
44
45 # Functions used later on
46 def batch(iterable, cols=1):
47 import math
48
49 length = len(iterable)
50 lines = math.ceil(length / cols)
51
52 for line in range(lines):
53 fields = []
54 for col in range(cols):
55 index = line + (col * lines)
56 if index < length:
57 fields.append(iterable[index])
58 yield fields
59
60
61 def getTerminalSize():
62 import os
63 env = os.environ
64
65 def ioctl_GWINSZ(fd):
66 try:
67 import fcntl
68 import termios
69 import struct
70 cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
71 '1234'))
72 return cr
73 except:
74 return
75
76 cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
77 if not cr:
78 try:
79 fd = os.open(os.ctermid(), os.O_RDONLY)
80 cr = ioctl_GWINSZ(fd)
81 os.close(fd)
82 except:
83 pass
84
85 if not cr:
86 cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
87
88 return int(cr[1]), int(cr[0])
89
90
91 def getSubContainers(container, lxcpath):
92 attach = ['lxc-attach', '-P', lxcpath, '-R', '-s', 'NETWORK|PID',
93 '-n', container,
94 '--', sys.argv[0], "--nesting"]
95
96 with open(os.devnull, "w") as fd:
97 newenv = dict(os.environ)
98 newenv['NESTED'] = "/proc/1/root/%s" % lxc.default_config_path
99 sp = subprocess.Popen(attach, stderr=fd, stdout=subprocess.PIPE,
100 env=newenv, universal_newlines=True)
101 sp.wait()
102 out = sp.stdout.read()
103 if out:
104 return json.loads(out)
105 return None
106
107
108 # Begin parsing the command line
109 parser = argparse.ArgumentParser(description=_("LXC: List containers"),
110 formatter_class=argparse.RawTextHelpFormatter)
111
112 parser.add_argument("-1", dest="one", action="store_true",
113 help=_("list one container per line (default when piped)"))
114
115 parser.add_argument("-P", "--lxcpath", dest="lxcpath", metavar="PATH",
116 help=_("Use specified container path"),
117 default=lxc.default_config_path)
118
119 parser.add_argument("--active", action="store_true",
120 help=_("list only active containers "
121 "(same as --running --frozen)"))
122
123 parser.add_argument("--frozen", dest="state", action="append_const",
124 const="FROZEN", help=_("list only frozen containers"))
125
126 parser.add_argument("--running", dest="state", action="append_const",
127 const="RUNNING", help=_("list only running containers"))
128
129 parser.add_argument("--stopped", dest="state", action="append_const",
130 const="STOPPED", help=_("list only stopped containers"))
131
132 parser.add_argument("--fancy", action="store_true",
133 help=_("use fancy output"))
134
135 parser.add_argument("--fancy-format", type=str, default="name,state,ipv4,ipv6",
136 help=_("comma separated list of fields to show"))
137
138 parser.add_argument("--nesting", dest="nesting", action="store_true",
139 help=_("show nested containers"))
140
141 parser.add_argument("filter", metavar='FILTER', type=str, nargs="?",
142 help=_("regexp to be applied on the container list"))
143
144 args = parser.parse_args()
145
146 # --active is the same as --running --frozen
147 if args.active:
148 if not args.state:
149 args.state = []
150 args.state += ["RUNNING", "FROZEN"]
151
152 # If the output is piped, default to --one
153 if not sys.stdout.isatty():
154 args.one = True
155
156 # Set the lookup path for the containers
157 lxcpath = os.environ.get('NESTED', args.lxcpath)
158
159 # Turn args.fancy_format into a list
160 args.fancy_format = args.fancy_format.strip().split(",")
161
162 # Basic checks
163 ## The user needs to be uid 0
164 if not os.geteuid() == 0 and (args.fancy or args.state):
165 parser.error(_("You must be root to access advanced container properties. "
166 "Try running: sudo %s"
167 % (sys.argv[0])))
168
169 # List of containers, stored as dictionaries
170 containers = []
171 for container_name in lxc.list_containers(config_path=lxcpath):
172 entry = {}
173 entry['name'] = container_name
174
175 # Apply filter
176 if args.filter and not re.match(args.filter, container_name):
177 continue
178
179 # Return before grabbing the object (non-root)
180 if not args.state and not args.fancy and not args.nesting:
181 containers.append(entry)
182 continue
183
184 container = lxc.Container(container_name, args.lxcpath)
185
186 # Filter by status
187 if args.state and container.state not in args.state:
188 continue
189
190 # Nothing more is needed if we're not printing some fancy output
191 if not args.fancy and not args.nesting:
192 containers.append(entry)
193 continue
194
195 # Some extra field we may want
196 if 'state' in args.fancy_format or args.nesting:
197 entry['state'] = container.state
198
199 if 'pid' in args.fancy_format or args.nesting:
200 entry['pid'] = "-"
201 if container.init_pid != -1:
202 entry['pid'] = str(container.init_pid)
203
204 # Get the IPs
205 for family, protocol in {'inet': 'ipv4', 'inet6': 'ipv6'}.items():
206 if protocol in args.fancy_format or args.nesting:
207 entry[protocol] = "-"
208 ips = container.get_ips(family=family)
209 if ips:
210 entry[protocol] = ", ".join(ips)
211
212 # Append the container
213 containers.append(entry)
214
215 # Nested containers
216 if args.nesting:
217 sub = getSubContainers(container_name, args.lxcpath)
218 if sub:
219 for entry in sub:
220 if 'nesting_parent' not in entry:
221 entry['nesting_parent'] = []
222 entry['nesting_parent'].insert(0, container_name)
223 entry['nesting_real_name'] = entry.get('nesting_real_name',
224 entry['name'])
225 entry['name'] = "%s/%s" % (container_name, entry['name'])
226 containers += sub
227
228 # Deal with json output:
229 if 'NESTED' in os.environ:
230 print(json.dumps(containers))
231 sys.exit(0)
232
233 # Print the list
234 ## Standard list with one entry per line
235 if not args.fancy and args.one:
236 for container in sorted(containers,
237 key=lambda container: container['name']):
238 print(container['name'])
239 sys.exit(0)
240
241 ## Standard list with multiple entries per line
242 if not args.fancy and not args.one:
243 # Get the longest name and extra simple list
244 field_maxlength = 0
245 container_names = []
246 for container in containers:
247 if len(container['name']) > field_maxlength:
248 field_maxlength = len(container['name'])
249 container_names.append(container['name'])
250
251 # Figure out how many we can put per line
252 width = getTerminalSize()[0]
253
254 entries = int(width / (field_maxlength + 2))
255 if entries == 0:
256 entries = 1
257
258 for line in batch(sorted(container_names), entries):
259 line_format = ""
260 for index in range(len(line)):
261 line_format += "{line[%s]:%s}" % (index, field_maxlength + 2)
262
263 print(line_format.format(line=line))
264
265 ## Fancy listing
266 if args.fancy:
267 field_maxlength = {}
268
269 # Get the maximum length per field
270 for field in args.fancy_format:
271 field_maxlength[field] = len(field)
272
273 for container in containers:
274 for field in args.fancy_format:
275 if field == 'name' and 'nesting_real_name' in container:
276 fieldlen = len(" " * ((len(container['nesting_parent']) - 1)
277 * 4) + " \_ " + container['nesting_real_name'])
278 if fieldlen > field_maxlength[field]:
279 field_maxlength[field] = fieldlen
280 elif len(container[field]) > field_maxlength[field]:
281 field_maxlength[field] = len(container[field])
282
283 # Generate the line format string based on the maximum length and
284 # a 2 character padding
285 line_format = ""
286 index = 0
287 for field in args.fancy_format:
288 line_format += "{fields[%s]:%s}" % (index, field_maxlength[field] + 2)
289 index += 1
290
291 # Get the line length minus the padding of the last field
292 line_length = -2
293 for field in field_maxlength:
294 line_length += field_maxlength[field] + 2
295
296 # Print header
297 print(line_format.format(fields=[header.upper()
298 for header in args.fancy_format]))
299 print("-" * line_length)
300
301 # Print the entries
302 for container in sorted(containers,
303 key=lambda container: container['name']):
304 fields = []
305 for field in args.fancy_format:
306 if field == 'name' and 'nesting_real_name' in container:
307 prefix = " " * ((len(container['nesting_parent']) - 1) * 4)
308 fields.append(prefix + " \_ " + container['nesting_real_name'])
309 else:
310 fields.append(container[field])
311
312 print(line_format.format(fields=fields))