fb-vmware 1.7.1__py3-none-any.whl → 1.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. fb_vmware/__init__.py +1 -1
  2. fb_vmware/app/__init__.py +285 -6
  3. fb_vmware/app/get_host_list.py +115 -100
  4. fb_vmware/app/get_network_list.py +176 -218
  5. fb_vmware/app/get_rpool_list.py +386 -0
  6. fb_vmware/app/get_storage_cluster_info.py +303 -0
  7. fb_vmware/app/get_storage_cluster_list.py +100 -107
  8. fb_vmware/app/get_storage_list.py +145 -112
  9. fb_vmware/app/get_vm_info.py +79 -17
  10. fb_vmware/app/get_vm_list.py +169 -95
  11. fb_vmware/app/search_storage.py +470 -0
  12. fb_vmware/argparse_actions.py +78 -0
  13. fb_vmware/base.py +28 -1
  14. fb_vmware/cluster.py +99 -7
  15. fb_vmware/connect.py +450 -20
  16. fb_vmware/datastore.py +195 -6
  17. fb_vmware/dc.py +19 -1
  18. fb_vmware/ds_cluster.py +215 -2
  19. fb_vmware/dvs.py +37 -1
  20. fb_vmware/errors.py +31 -10
  21. fb_vmware/host.py +40 -2
  22. fb_vmware/host_port_group.py +1 -2
  23. fb_vmware/network.py +17 -1
  24. fb_vmware/obj.py +30 -1
  25. fb_vmware/vm.py +19 -1
  26. fb_vmware/xlate.py +8 -13
  27. fb_vmware-1.8.1.data/data/share/locale/de/LC_MESSAGES/fb_vmware.mo +0 -0
  28. fb_vmware-1.8.1.data/data/share/locale/en/LC_MESSAGES/fb_vmware.mo +0 -0
  29. {fb_vmware-1.7.1.dist-info → fb_vmware-1.8.1.dist-info}/METADATA +2 -1
  30. fb_vmware-1.8.1.dist-info/RECORD +40 -0
  31. {fb_vmware-1.7.1.dist-info → fb_vmware-1.8.1.dist-info}/entry_points.txt +3 -0
  32. fb_vmware-1.7.1.data/data/share/locale/de_DE/LC_MESSAGES/fb_vmware.mo +0 -0
  33. fb_vmware-1.7.1.data/data/share/locale/en_US/LC_MESSAGES/fb_vmware.mo +0 -0
  34. fb_vmware-1.7.1.dist-info/RECORD +0 -36
  35. {fb_vmware-1.7.1.dist-info → fb_vmware-1.8.1.dist-info}/WHEEL +0 -0
  36. {fb_vmware-1.7.1.dist-info → fb_vmware-1.8.1.dist-info}/licenses/LICENSE +0 -0
fb_vmware/__init__.py CHANGED
@@ -56,7 +56,7 @@ from .vm import VsphereVmList
56
56
  from .xlate import XLATOR
57
57
 
58
58
 
59
- __version__ = "1.7.1"
59
+ __version__ = "1.8.1"
60
60
 
61
61
  LOG = logging.getLogger(__name__)
62
62
 
fb_vmware/app/__init__.py CHANGED
@@ -25,10 +25,14 @@ from fb_tools.multi_config import DEFAULT_ENCODING
25
25
 
26
26
  import pytz
27
27
 
28
+ from rich.console import Console
29
+ from rich.prompt import InvalidResponse, Prompt, PromptBase, PromptType
30
+
28
31
  # Own modules
29
32
  from .. import __version__ as GLOBAL_VERSION
30
33
  from ..config import VmwareConfiguration
31
34
  from ..connect import VsphereConnection
35
+ from ..ds_cluster import VsphereDsCluster
32
36
  from ..errors import VSphereExpectedError
33
37
  from ..xlate import DOMAIN
34
38
  from ..xlate import LOCALE_DIR
@@ -38,13 +42,18 @@ from ..xlate import __lib_dir__ as __xlate_lib_dir__
38
42
  from ..xlate import __mo_file__ as __xlate_mo_file__
39
43
  from ..xlate import __module_dir__ as __xlate_module_dir__
40
44
 
41
- __version__ = "1.3.0"
45
+ __version__ = "1.7.2"
42
46
  LOG = logging.getLogger(__name__)
43
47
  TZ = pytz.timezone("Europe/Berlin")
44
48
 
45
49
  _ = XLATOR.gettext
46
50
  ngettext = XLATOR.ngettext
47
51
 
52
+ Prompt.validate_error_message = "[prompt.invalid]" + _("Please enter a valid value")
53
+ Prompt.illegal_choice_message = "[prompt.invalid.choice]" + _(
54
+ "Please select one of the available options"
55
+ )
56
+
48
57
 
49
58
  # =============================================================================
50
59
  class VmwareAppError(FbAppError):
@@ -53,10 +62,56 @@ class VmwareAppError(FbAppError):
53
62
  pass
54
63
 
55
64
 
65
+ # =============================================================================
66
+ class PositiveIntPrompt(PromptBase[int]):
67
+ """A prompt that returns an positive integer greater than zero.
68
+
69
+ Example:
70
+ >>> burrito_count = PositiveIntPrompt.ask("How many burritos do you want to order")
71
+
72
+ """
73
+
74
+ response_type = int
75
+ validate_error_message = "[prompt.invalid]" + _(
76
+ "Please enter a valid positive integer number greater than zero."
77
+ )
78
+
79
+ # -------------------------------------------------------------------------
80
+ def process_response(self, value: str) -> PromptType:
81
+ """Process response from user, convert to prompt type.
82
+
83
+ Args:
84
+ value (str): String typed by user.
85
+
86
+ Raises:
87
+ InvalidResponse: If ``value`` is invalid.
88
+
89
+ Returns:
90
+ PromptType: The value to be returned from ask method.
91
+ """
92
+ value = value.strip()
93
+ try:
94
+ return_value: PromptType = self.response_type(value)
95
+ if return_value <= 0:
96
+ raise InvalidResponse(self.validate_error_message)
97
+ except ValueError:
98
+ raise InvalidResponse(self.validate_error_message)
99
+
100
+ return return_value
101
+
102
+
56
103
  # =============================================================================
57
104
  class BaseVmwareApplication(FbConfigApplication):
58
105
  """Base class for all VMware/vSphere application classes."""
59
106
 
107
+ term_colors = {
108
+ "kitty": "256",
109
+ "256color": "256",
110
+ "16color": "standard",
111
+ }
112
+
113
+ default_all_vspheres = True
114
+
60
115
  # -------------------------------------------------------------------------
61
116
  def __init__(
62
117
  self,
@@ -81,6 +136,7 @@ class BaseVmwareApplication(FbConfigApplication):
81
136
  """Initialize a BaseVmwareApplication object."""
82
137
  self.req_vspheres = None
83
138
  self.do_vspheres = []
139
+ self.rich_console = None
84
140
 
85
141
  if base_dir is None:
86
142
  base_dir = pathlib.Path(os.getcwd()).resolve()
@@ -107,7 +163,7 @@ class BaseVmwareApplication(FbConfigApplication):
107
163
  # -------------------------------------------------------------------------
108
164
  def __del__(self):
109
165
  """Clean up in emergency case."""
110
- if self.vsphere.keys():
166
+ if hasattr(self, "vsphere") and self.vsphere.keys():
111
167
  self.cleaning_up()
112
168
 
113
169
  # -------------------------------------------------------------------------
@@ -153,6 +209,23 @@ class BaseVmwareApplication(FbConfigApplication):
153
209
  if self.verbose > 2:
154
210
  LOG.debug(_("{what} of {app} ...").format(what="post_init()", app=self.appname))
155
211
 
212
+ args_color = getattr(self.args, "color", "auto")
213
+ if args_color == "auto":
214
+ self.rich_console = Console()
215
+ else:
216
+ color_system = None
217
+ if args_color == "yes":
218
+ color_term = os.environ.get("COLORTERM", "").strip().lower()
219
+ if color_term in ("truecolor", "24bit"):
220
+ color_system = "truecolor"
221
+ else:
222
+ color_system = "standard"
223
+ term = os.environ.get("TERM", "").strip().lower()
224
+ _term_name, _hyphen, colors = term.rpartition("-")
225
+ color_system = self.term_colors.get(colors, "standard")
226
+
227
+ self.rich_console = Console(color_system=color_system)
228
+
156
229
  if not self.cfg.vsphere.keys():
157
230
  msg = _("Did not found any configured vSphere environments.")
158
231
  LOG.error(msg)
@@ -178,18 +251,22 @@ class BaseVmwareApplication(FbConfigApplication):
178
251
 
179
252
  if self.req_vspheres:
180
253
  self.do_vspheres = copy.copy(self.req_vspheres)
181
- else:
254
+ elif self.default_all_vspheres:
182
255
  for vs_name in self.cfg.vsphere.keys():
183
256
  self.do_vspheres.append(vs_name)
184
257
 
185
- self.init_vsphere_handlers()
186
-
187
258
  # -------------------------------------------------------------------------
188
259
  def init_arg_parser(self):
189
260
  """Initiate the argument parser."""
261
+ self.add_vsphere_argument()
190
262
  super(BaseVmwareApplication, self).init_arg_parser()
191
263
 
192
- self.arg_parser.add_argument(
264
+ # -------------------------------------------------------------------------
265
+ def add_vsphere_argument(self):
266
+ """Add a commandline option for selecting the vSphere to use."""
267
+ vsphere_options = self.arg_parser.add_argument_group(_("vSphere options"))
268
+
269
+ vsphere_options.add_argument(
193
270
  "--vs",
194
271
  "--vsphere",
195
272
  dest="req_vsphere",
@@ -203,6 +280,208 @@ class BaseVmwareApplication(FbConfigApplication):
203
280
  if self.verbose > 2:
204
281
  LOG.debug(_("Got command line arguments:") + "\n" + pp(self.args))
205
282
 
283
+ # -------------------------------------------------------------------------
284
+ def pre_run(self):
285
+ """Execute some actions before the main routine."""
286
+ LOG.debug(_("Actions before running main routine."))
287
+
288
+ self.init_vsphere_handlers()
289
+
290
+ # -------------------------------------------------------------------------
291
+ def select_storage_type(self, storage_type=None):
292
+ """Select a storage type for a virtual disk to create."""
293
+ types = {}
294
+ type_list = []
295
+ for st_type in VsphereDsCluster.valid_storage_types:
296
+ types[st_type.lower()] = st_type
297
+ type_list.append(st_type.lower())
298
+ types["any"] = "Any"
299
+ type_list.append("any")
300
+
301
+ if storage_type is not None:
302
+ if self.verbose > 2:
303
+ LOG.debug(f"Checking for storage type {storage_type!r} ...")
304
+ st_type = storage_type.lower()
305
+ if st_type in types:
306
+ return types[st_type]
307
+ msg = _("Invalid storage type {} given.").format(self.colored(st_type, "RED"))
308
+ raise VmwareAppError(msg)
309
+
310
+ if len(types) == 1:
311
+ idx = [types.keys()][0]
312
+ st_type = types[idx]
313
+ if self.verbose > 0:
314
+ LOG.debug(
315
+ f"Automatic select of storage type {st_type!r}, because it is the only one."
316
+ )
317
+ return st_type
318
+
319
+ st_type = Prompt.ask(
320
+ _("Select a storage type to search for the a storage location"),
321
+ choices=type_list,
322
+ show_choices=True,
323
+ case_sensitive=False,
324
+ console=self.rich_console,
325
+ )
326
+
327
+ return types[st_type.lower()]
328
+
329
+ # -------------------------------------------------------------------------
330
+ def select_vsphere(self):
331
+ """Select exact one of the configured vSpheres."""
332
+ if self.do_vspheres and len(self.do_vspheres) == 1:
333
+ return self.do_vspheres[0]
334
+
335
+ if self.do_vspheres:
336
+ msg = _("There are multiple vSpheres selected on commandline.")
337
+ raise VmwareAppError(msg)
338
+
339
+ if not self.cfg.vsphere.keys():
340
+ msg = _("There are no configured vSpheres available.")
341
+ raise VmwareAppError(msg)
342
+
343
+ vspheres = []
344
+ for vs_name in self.cfg.vsphere.keys():
345
+ vspheres.append(vs_name)
346
+
347
+ vsphere = Prompt.ask(
348
+ _("Select the vSphere to search for the a storage location"),
349
+ choices=vspheres,
350
+ show_choices=True,
351
+ console=self.rich_console,
352
+ )
353
+
354
+ return vsphere
355
+
356
+ # -------------------------------------------------------------------------
357
+ def select_datacenter(self, vs_name, dc_name=None):
358
+ """Select a virtual datacenter from given vSphere."""
359
+ if not vs_name:
360
+ raise VmwareAppError(_("No vSphere name given."))
361
+ if vs_name not in self.vsphere:
362
+ raise VmwareAppError(
363
+ _("vSphere {} is not an active vSphere.").format(self.colored(vs_name, "RED"))
364
+ )
365
+
366
+ vsphere = self.vsphere[vs_name]
367
+ vsphere.get_datacenters()
368
+ dc_list = []
369
+ for _dc_name in vsphere.datacenters.keys():
370
+ dc_list.append(_dc_name)
371
+
372
+ if not len(dc_list):
373
+ msg = _("Did not found virtual datacenters in vSphere {}.").format(
374
+ self.colored(vs_name, "RED")
375
+ )
376
+ LOG.error(msg)
377
+ return None
378
+
379
+ if self.verbose > 2:
380
+ LOG.debug(f"Found datacenters in vSphere {vs_name}:\n" + pp(dc_list))
381
+
382
+ if dc_name:
383
+ if dc_name in dc_list:
384
+ return dc_name
385
+ msg = _("Datacenter {dc} does not exists in vSphere {vs}.").format(
386
+ dc=self.colored(dc_name, "RED"), vs=self.colored(vs_name, "CYAN")
387
+ )
388
+ LOG.error(msg)
389
+ return None
390
+
391
+ if len(dc_list) == 1:
392
+ if self.verbose > 0:
393
+ LOG.debug(
394
+ f"Automatic select of datacenter {dc_list[0]!r}, because it is the only one."
395
+ )
396
+ return dc_list[0]
397
+
398
+ dc_name = Prompt.ask(
399
+ _("Select a virtual datacenter to search for the storage location"),
400
+ choices=sorted(dc_list, key=str.lower),
401
+ show_choices=True,
402
+ console=self.rich_console,
403
+ )
404
+
405
+ return dc_name
406
+
407
+ # -------------------------------------------------------------------------
408
+ def select_computing_cluster(self, vs_name, dc_name, cluster_name=None):
409
+ """Select a cluster computing resource or computing resource in a datacenter."""
410
+ if not vs_name:
411
+ raise VmwareAppError(_("No vSphere name given."))
412
+ if vs_name not in self.vsphere:
413
+ raise VmwareAppError(
414
+ _("vSphere {} is not an active vSphere.").format(self.colored(vs_name, "RED"))
415
+ )
416
+ vsphere = self.vsphere[vs_name]
417
+
418
+ if not dc_name:
419
+ raise VmwareAppError(_("No virtual datacenter name given."))
420
+ vsphere.get_datacenters()
421
+ if dc_name not in vsphere.datacenters:
422
+ msg = _("Datacenter {dc} not found in vSphere {vs}.").format(
423
+ dc=self.colored(dc_name, "RED"), vs=self.colored(vs_name, "CYAN")
424
+ )
425
+
426
+ cluster_list = []
427
+ cluster_type = {}
428
+ vsphere.get_clusters(search_in_dc=dc_name)
429
+ for _cluster in vsphere.clusters:
430
+ cluster_list.append(_cluster.name)
431
+ cluster_type[_cluster.name] = _("cluster computing resource")
432
+ if _cluster.standalone:
433
+ cluster_type[_cluster.name] = _("host computing resource")
434
+
435
+ if not len(cluster_list):
436
+ msg = _("Did not found computing resources in dc {dc} in vSphere {vs}.").format(
437
+ dc=self.colored(dc_name, "RED"),
438
+ vs=self.colored(vs_name, "RED"),
439
+ )
440
+ LOG.error(msg)
441
+ return (None, None)
442
+
443
+ if self.verbose > 1:
444
+ msg = f"Found computing resources in datacenter {dc_name} in vSphere {vs_name}:\n"
445
+ msg += pp(cluster_type)
446
+ LOG.debug(msg)
447
+
448
+ if cluster_name:
449
+ if cluster_name in cluster_list:
450
+ return (cluster_name, cluster_type[cluster_name])
451
+ msg = _(
452
+ "Computing resource {cl} does not exists in datacenter {dc} in vSphere {vs}."
453
+ ).format(
454
+ cl=self.colored(cluster_name, "RED"),
455
+ dc=self.colored(dc_name, "CYAN"),
456
+ vs=self.colored(vs_name, "CYAN"),
457
+ )
458
+ LOG.error(msg)
459
+ return (None, None)
460
+
461
+ if len(cluster_list) == 1:
462
+ if self.verbose > 0:
463
+ LOG.debug(
464
+ f"Automatic select of computing resource {cluster_list[0]!r}, "
465
+ "because it is the only one."
466
+ )
467
+ cluster_name = cluster_list[0]
468
+ return (cluster_name, cluster_type[cluster_name])
469
+
470
+ cluster_name = Prompt.ask(
471
+ _("Select a computing resource, which should be connected with the storage location"),
472
+ choices=sorted(cluster_list, key=str.lower),
473
+ show_choices=True,
474
+ console=self.rich_console,
475
+ )
476
+
477
+ return (cluster_name, cluster_type[cluster_name])
478
+
479
+ # -------------------------------------------------------------------------
480
+ def prompt_for_disk_size(self):
481
+ """Ask for the size of a virtual disk in GiByte."""
482
+ disk_size_gb = PositiveIntPrompt.ask(_("Get the size of the virtual disk in GiByte"))
483
+ return disk_size_gb
484
+
206
485
  # -------------------------------------------------------------------------
207
486
  def init_vsphere_handlers(self):
208
487
  """Initialize all vSphere handlers."""
@@ -18,18 +18,25 @@ import sys
18
18
  from operator import itemgetter
19
19
 
20
20
  # Third party modules
21
+ from babel.numbers import format_decimal
22
+
21
23
  from fb_tools.argparse_actions import RegexOptionAction
22
24
  from fb_tools.common import pp
23
25
  from fb_tools.spinner import Spinner
24
26
  from fb_tools.xlate import format_list
25
27
 
28
+ from rich import box
29
+ from rich.table import Table
30
+ from rich.text import Text
31
+
26
32
  # Own modules
27
33
  from . import BaseVmwareApplication, VmwareAppError
28
34
  from .. import __version__ as GLOBAL_VERSION
29
35
  from ..errors import VSphereExpectedError
36
+ from ..host import VsphereHost
30
37
  from ..xlate import XLATOR
31
38
 
32
- __version__ = "1.4.0"
39
+ __version__ = "1.5.6"
33
40
  LOG = logging.getLogger(__name__)
34
41
 
35
42
  _ = XLATOR.gettext
@@ -66,9 +73,7 @@ class GetHostsListApplication(BaseVmwareApplication):
66
73
  env_prefix=None,
67
74
  ):
68
75
  """Initialize a GetHostsListApplication object."""
69
- desc = _(
70
- "Tries to get a list of all physical hosts in " "VMware vSphere and print it out."
71
- )
76
+ desc = _("Tries to get a list of all physical hosts in VMware vSphere and print it out.")
72
77
 
73
78
  self._host_pattern = self.default_host_pattern
74
79
  self.sort_keys = self.default_sort_keys
@@ -112,8 +117,6 @@ class GetHostsListApplication(BaseVmwareApplication):
112
117
  # -------------------------------------------------------------------------
113
118
  def init_arg_parser(self):
114
119
  """Public available method to initiate the argument parser."""
115
- super(GetHostsListApplication, self).init_arg_parser()
116
-
117
120
  filter_group = self.arg_parser.add_argument_group(_("Filter options"))
118
121
 
119
122
  filter_group.add_argument(
@@ -165,6 +168,8 @@ class GetHostsListApplication(BaseVmwareApplication):
165
168
  ),
166
169
  )
167
170
 
171
+ super(GetHostsListApplication, self).init_arg_parser()
172
+
168
173
  # -------------------------------------------------------------------------
169
174
  def perform_arg_parser(self):
170
175
  """Evaluate command line parameters."""
@@ -206,10 +211,10 @@ class GetHostsListApplication(BaseVmwareApplication):
206
211
  ret = 0
207
212
  all_hosts = []
208
213
 
209
- if self.verbose:
214
+ if self.verbose or self.quiet:
210
215
  for vsphere_name in self.vsphere:
211
216
  all_hosts += self.get_hosts(vsphere_name)
212
- elif not self.quiet:
217
+ else:
213
218
  spin_prompt = _("Getting all vSphere hosts ...") + " "
214
219
  spinner_name = self.get_random_spinner_name()
215
220
  with Spinner(spin_prompt, spinner_name):
@@ -269,6 +274,7 @@ class GetHostsListApplication(BaseVmwareApplication):
269
274
  summary["online"] = host.online
270
275
  summary["no_portgroups"] = str(len(host.portgroups))
271
276
  summary["power_state"] = host.power_state
277
+ summary["standby"] = host.standby
272
278
  summary["os_name"] = host.product.name
273
279
  summary["os_version"] = host.product.os_version
274
280
  summary["quarantaine"] = host.quarantaine
@@ -278,108 +284,113 @@ class GetHostsListApplication(BaseVmwareApplication):
278
284
  # -------------------------------------------------------------------------
279
285
  def print_hosts(self, hosts):
280
286
  """Print on STDOUT all information about all hosts in a human readable format."""
281
- labels = {
282
- "vsphere": "vSphere",
283
- "dc": "DC",
284
- "cluster": "Cluster",
285
- "name": "Host",
286
- "connection_state": _("Connect state"),
287
- "cpus": _("CPU cores/threads"),
288
- "memory_gb": _("Memory in GiB"),
289
- "vendor": _("Vendor"),
290
- "model": _("Model"),
291
- "maintenance": _("Maintenance"),
292
- "online": _("Online"),
293
- # 'no_portgroups': _('Portgroups'),
294
- "power_state": _("Power State"),
295
- "os_name": _("OS Name"),
296
- "os_version": _("OS Version"),
297
- # 'quarantaine': _('Quarantaine'),
298
- }
299
-
300
- label_list = (
301
- "name",
302
- "dc",
303
- "vsphere",
304
- "cluster",
305
- "vendor",
306
- "model",
307
- "os_name",
308
- "os_version",
309
- "cpus",
310
- "memory_gb",
311
- "power_state",
312
- "connection_state",
313
- "online",
314
- "maintenance",
287
+ hosts.sort(key=itemgetter(*self.sort_keys))
288
+
289
+ show_header = True
290
+ table_title = _("All physical hosts") + "\n"
291
+ box_style = box.ROUNDED
292
+ if self.quiet:
293
+ show_header = False
294
+ table_title = None
295
+ box_style = None
296
+
297
+ if self.quiet:
298
+ caption = None
299
+ else:
300
+ count = len(hosts)
301
+ if count:
302
+ caption = "\n" + ngettext(
303
+ "Found one VMware host.",
304
+ "Found {} VMware hosts.",
305
+ count,
306
+ ).format(count)
307
+ else:
308
+ caption = "\n" + _("Found no VMware hosts.")
309
+
310
+ table = Table(
311
+ title=table_title,
312
+ title_style="bold cyan",
313
+ caption=caption,
314
+ caption_style="default on default",
315
+ caption_justify="left",
316
+ box=box_style,
317
+ show_header=show_header,
318
+ show_footer=False,
315
319
  )
316
320
 
317
- str_lengths = {}
318
- for label in labels:
319
- str_lengths[label] = len(labels[label])
321
+ table.add_column(header=_("Host"))
322
+ table.add_column(header=_("vSphere"))
323
+ table.add_column(header=_("Data Center"))
324
+ table.add_column(header=_("Cluster"))
325
+ table.add_column(header=_("Vendor"))
326
+ table.add_column(header=_("Model"))
327
+ table.add_column(header=_("OS Name"))
328
+ table.add_column(header=_("OS Version"))
329
+ table.add_column(header=_("CPU cores/threads"), justify="right")
330
+ table.add_column(header=_("Memory in GiB"), justify="right")
331
+ table.add_column(header=_("Power State"))
332
+ table.add_column(header=_("Connect state"))
333
+ table.add_column(header=_("StandBy state"), justify="center")
334
+ table.add_column(header=_("Maintenance"), justify="center")
320
335
 
321
- max_len = 0
322
- count = 0
323
336
  for host in hosts:
324
- for label in labels.keys():
325
- val = host[label]
326
- if val is None:
327
- val = "-"
328
- host[label] = val
329
- else:
330
- if label == "memory_gb":
331
- val = "{:7.1f}".format(val)
332
- host[label] = val
333
- elif label in ("connection_state", "maintenance", "online", "quarantaine"):
334
- if val:
335
- val = _("Yes")
336
- else:
337
- val = _("No")
338
- host[label] = val
339
- if len(val) > str_lengths[label]:
340
- str_lengths[label] = len(val)
341
-
342
- for label in labels.keys():
343
- if max_len:
344
- max_len += 2
345
- max_len += str_lengths[label]
346
-
347
- if self.verbose > 1:
348
- LOG.debug("Label length:\n" + pp(str_lengths))
349
- LOG.debug("Max line length: {} chars".format(max_len))
350
-
351
- tpl = ""
352
- for label in label_list:
353
- if tpl != "":
354
- tpl += " "
355
- if label in ("memory_gb", "cpus", "no_portgroups"):
356
- tpl += "{{{la}:>{le}}}".format(la=label, le=str_lengths[label])
337
+ row = []
338
+
339
+ row.append(host["name"])
340
+ row.append(host["vsphere"])
341
+ row.append(host["dc"])
342
+ row.append(host["cluster"])
343
+ row.append(host["vendor"])
344
+ row.append(host["model"])
345
+ row.append(host["os_name"])
346
+ row.append(host["os_version"])
347
+ row.append(host["cpus"])
348
+ row.append(format_decimal(host["memory_gb"], format="#,##0"))
349
+
350
+ power_state = host["power_state"]
351
+ if power_state in VsphereHost.power_state_label:
352
+ power_state = VsphereHost.power_state_label[power_state]
353
+ p_state = Text(power_state)
354
+ if host["power_state"].lower() == "poweredon":
355
+ p_state.stylize("bold green")
356
+ elif host["power_state"].lower() == "poweredoff":
357
+ p_state.stylize("bold red")
358
+ elif host["power_state"].lower() == "standby":
359
+ p_state.stylize("bold blue")
357
360
  else:
358
- tpl += "{{{la}:<{le}}}".format(la=label, le=str_lengths[label])
359
- if self.verbose > 1:
360
- LOG.debug(_("Line template: {}").format(tpl))
361
+ p_state.stylize("bold magenta")
362
+ row.append(p_state)
363
+
364
+ connection_state = host["connection_state"]
365
+ if connection_state in VsphereHost.connect_state_label:
366
+ connection_state = VsphereHost.connect_state_label[connection_state]
367
+ c_state = Text(connection_state)
368
+ if host["connection_state"].lower() == "connected":
369
+ c_state.stylize("bold green")
370
+ elif host["connection_state"].lower() == "disconnected":
371
+ c_state.stylize("bold red")
372
+ else:
373
+ c_state.stylize("bold magenta")
374
+ row.append(c_state)
361
375
 
362
- if not self.quiet:
363
- print()
364
- print(tpl.format(**labels))
365
- print("-" * max_len)
376
+ standby = host["standby"]
377
+ if standby == "none":
378
+ standby = "~"
379
+ if standby in VsphereHost.standby_mode_label:
380
+ standby = VsphereHost.standby_mode_label[standby]
381
+ row.append(standby)
366
382
 
367
- hosts.sort(key=itemgetter(*self.sort_keys))
383
+ m_state = Text(_("No"), style="bold green")
384
+ if host["maintenance"]:
385
+ m_state = Text(_("Yes"), style="bold yellow")
386
+ row.append(m_state)
368
387
 
369
- for host in hosts:
370
- count += 1
371
- print(tpl.format(**host))
388
+ table.add_row(*row)
389
+
390
+ self.rich_console.print(table)
372
391
 
373
392
  if not self.quiet:
374
393
  print()
375
- if count == 0:
376
- msg = _("Found no VMware hosts.")
377
- else:
378
- msg = ngettext("Found one VMware host.", "Found {} VMware hosts.", count).format(
379
- count
380
- )
381
- print(msg)
382
- print()
383
394
 
384
395
  # -------------------------------------------------------------------------
385
396
  def get_hosts(self, vsphere_name):
@@ -416,7 +427,11 @@ def main():
416
427
  if app.verbose > 2:
417
428
  print(_("{c}-Object:\n{a}").format(c=app.__class__.__name__, a=app), file=sys.stderr)
418
429
 
419
- app()
430
+ try:
431
+ app()
432
+ except KeyboardInterrupt:
433
+ print("\n" + app.colored(_("User interrupt."), "YELLOW"))
434
+ sys.exit(5)
420
435
 
421
436
  sys.exit(0)
422
437