1. Pior Bastida
  2. awstools

Commits

Pior Bastida  committed b253aa5

Improve cfn console output (easier to read)

  • Participants
  • Parent commits ba44ea5
  • Branches default

Comments (0)

Files changed (4)

File awstools/commands/cloudformation.py

View file
 import boto
 from boto.exception import BotoServerError
 
-from awstools.display import (format_stack_summary,
-                              format_stack_summary_short,
+from awstools.display import (format_stack_summary, format_stack_outputs,
+                              format_stacks, format_stack_resources,
+                              format_stack_parameters,
                               format_stack_events)
 from awstools.utils.cloudformation import (find_stacks,
                                            find_one_stack)
                          delete,
                          info,
                          outputs,
+                         parameters,
                          resources,
                          events,
                          activities,])
     list stacks
     """
     stacks = find_stacks(args.stack_name, findall=args.all)
-    for stack in stacks:
-        yield format_stack_summary_short(stack)
+    yield format_stacks(stacks)
 
 
 @arg('stack_name', help=HELP_SN)
     args.template = None
 
     stacks = find_stacks(args.stack_name)
-    for stack in stacks:
-        yield format_stack_summary_short(stack)
+    yield format_stacks(stacks)
 
     confirm_action(arg, default=False)
 
     """
     stack = find_one_stack(args.stack_name, summary=False)
 
-    yield format_stack_summary(stack) + '\n'
-
-    for param in stack.parameters:
-        yield str(param)
+    yield format_stack_summary(stack)
     yield ''
 
-    for output in stack.outputs:
-        yield str(output)
+    yield format_stack_parameters(stack)
     yield ''
 
-    yield format_stack_events(stack, limit=10) + '\n'
+    yield format_stack_outputs(stack)
+    yield ''
 
-    for resource in stack.describe_resources():
-        yield "{r}\n  {r.resource_status} {r.physical_resource_id}".format(
-            r=resource)
+    yield format_stack_events(stack, limit=10)
     yield ''
 
+    yield format_stack_resources(stack)
+    yield '\n'
+
 
 @arg('stack_name', help=HELP_SN)
 @wrap_errors([ValueError, BotoServerError])
     """
     stack = find_one_stack(args.stack_name, summary=False)
 
-    yield format_stack_summary(stack) + '\n'
+    yield format_stack_summary(stack)
+    yield ''
 
-    for output in stack.outputs:
-        yield str(output)
+    yield format_stack_outputs(stack)
+
+
+@arg('stack_name', help=HELP_SN)
+@wrap_errors([ValueError, BotoServerError])
+def parameters(args):
+    """
+    display parameters of a stack
+    """
+    stack = find_one_stack(args.stack_name, summary=False)
+
+    yield format_stack_summary(stack)
+    yield ''
+
+    yield format_stack_parameters(stack)
 
 
 @arg('stack_name', help=HELP_SN)
     """
     stack = find_one_stack(args.stack_name, summary=False)
 
-    yield format_stack_summary(stack) + '\n'
+    yield format_stack_summary(stack)
+    yield ''
 
-    tmpl = "  ".join([
-                     "{r.logical_resource_id:<24}",
-                     "{r.physical_resource_id:<60}",
-                     "[{r.resource_status}] {r.resource_type}"
-                     ])
+    yield format_stack_resources(stack)
+    yield ''
 
-    for resource in stack.describe_resources():
-        yield tmpl.format(r=resource)
-    yield ''
 
 
 @arg('stack_name', help=HELP_SN)
     stack = find_one_stack(args.stack_name, summary=False)
     yield format_stack_summary(stack) + '\n'
     if args.all:
-        yield format_stack_events(stack) + '\n'
+        yield format_stack_events(stack)
     else:
-        yield format_stack_events(stack, limit=20) + '\n'
+        yield format_stack_events(stack, limit=20)
+    yield ''
 
 
 def activities(args):
     display global activity
     """
     stacks = find_stacks(None, findall=True)
-    for stack in stacks:
-        if stack.stack_status.endswith('_COMPLETE'):
-            continue
-        yield format_stack_summary_short(stack)
-
-
+    stacks = [s for s in stacks if not s.stack_status.endswith('_COMPLETE')]
+    yield format_stacks(stacks)
+    yield ''

File awstools/display.py

View file
 # Author: Pior Bastida <pbastida@ludia.com>
 
 import boto
+import arrow
+from prettytable import PrettyTable
 
 from awstools.utils.cloudformation import (find_one_resource,
                                            RES_TYPE_ASG,
                                            RES_TYPE_ELB)
 
 
+def humanize_date(d):
+    return arrow.get(d).humanize()
 
+def local_date(d):
+    return arrow.get(d).to('local').format('YYYY-MM-DD HH:mm:ss')
+
+def long_date(d):
+    return "%s (%s)" % (
+        arrow.get(d).to('local').format('YYYY-MM-DD HH:mm:ss'),
+        arrow.get(d).humanize(),
+    )
 
 def format_stack_summary(stack):
     tmpl = "Name: {s.stack_name}\n"
     tmpl += "Id: {s.stack_id}\n"
-    tmpl += "Status: {s.stack_status}\n"
-    tmpl += "Creation : {s.creation_time}\n"
+    tmpl += "Status: {s.stack_status} \n"
+    tmpl += "Creation : {date}\n"
 
     if hasattr(stack, 'description'):
         tmpl += "Template: {s.description}"
         tmpl += "Template: {s.template_description}"
     else:
         raise ValueError("Invalid Stack object")
-    return tmpl.format(s=stack)
+    return tmpl.format(s=stack, date=long_date(stack.creation_time))
 
 
-def format_stack_summary_short(stack):
-    tmpl = u"{s.stack_name:<26} {s.stack_status:<18} {s.creation_time}"
+def format_stacks(stacks):
+    tab = PrettyTable(['Name', 'Template', 'Status', 'Time'])
+    tab.align = 'l'
 
-    if hasattr(stack, 'description'):
-        tmpl += u" - {s.description}"
-    elif hasattr(stack, 'template_description'):
-        tmpl += u" - {s.template_description}"
-    else:
-        raise ValueError("Invalid Stack object")
-    return tmpl.format(s=stack)
+
+    for s in stacks:
+        tab.add_row([
+            s.stack_name,
+            s.template_description,
+            s.stack_status,
+            local_date(s.creation_time),
+        ])
+
+    return tab.get_string()
 
 
 def format_stack_events(stack, limit=None):
+    if hasattr(stack, 'describe_events'):
+        events = stack.describe_events()
+    else:
+        cfn = boto.connect_cloudformation()
+        events = cfn.describe_stack_events(stack.stack_name)
+
     cfn = boto.connect_cloudformation()
     events = list(cfn.describe_stack_events(stack.stack_name))
-    if limit:
-        events = events[0:limit]
 
-    def formatline(e):
-        f = "{time}  {etype:<40}  {logicalid:<24}  {status:<20}  {reason}"
-        return f.format(time=e.timestamp.isoformat().replace('T', ' '),
-                        status=e.resource_status,
-                        etype=e.resource_type,
-                        logicalid=e.logical_resource_id,
-                        reason=e.resource_status_reason)
+    if limit is None:
+        limit = len(events)
 
-    return "\n".join([formatline(e) for e in events])
+    tab = PrettyTable(['Time', 'Type', 'Logical ID', 'Status', 'Reason'])
+    tab.align = 'l'
+
+    for e in events:
+        reason = e.resource_status_reason
+
+        tab.add_row([local_date(e.timestamp),
+                    e.resource_type,
+                    e.logical_resource_id,
+                    e.resource_status,
+                    reason if reason is not None else ''])
+
+    return tab.get_string(end=limit)
+
+
+def format_stack_resources(stack):
+    if hasattr(stack, 'describe_resources'):
+        resources = stack.describe_resources()
+    else:
+        cfn = boto.connect_cloudformation()
+        resources = cfn.describe_stack_resources(stack.stack_name)
+
+    tab = PrettyTable(['Type', 'Status', 'Logical ID', 'Physical ID'])
+    tab.align = 'l'
+    tab.sortby = 'Type'
+
+    for r in resources:
+        if r.resource_type == 'AWS::CloudFormation::WaitConditionHandle':
+            physical_resource_id = r.physical_resource_id[0:45] + '...'
+        else:
+            physical_resource_id = r.physical_resource_id
+
+        tab.add_row([
+            r.resource_type,
+            r.resource_status,
+            r.logical_resource_id,
+            physical_resource_id])
+
+    return tab.get_string()
+
+
+def format_stack_outputs(stack):
+    tab = PrettyTable(['Key', 'Value', 'Description'])
+    tab.align = 'l'
+    tab.sortby = 'Key'
+
+    for o in stack.outputs:
+        tab.add_row([o.key, o.value, o.description])
+
+    return tab.get_string()
+
+
+def format_stack_parameters(stack):
+    tab = PrettyTable(['Key', 'Value'])
+    tab.align = 'l'
+    tab.sortby = 'Key'
+
+    for p in stack.parameters:
+        tab.add_row([p.key, p.value])
+
+    return tab.get_string()
 
 
 def format_autoscale(asg, detail=False):

File scripts/cfn

-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 Ludia Inc.
-# This software is licensed as described in the file LICENSE, which
-# you should have received as part of this distribution.
-# Author: Pior Bastida <pbastida@ludia.com>
-
-import pkg_resources
-
-from argh import ArghParser
-
-from awstools.commands import cloudformation
-
-
-HELP_CFG = "path of an alternative configuration file"
-HELP_SETTINGS = "path of the application settings configuration file"
-
-
-parser = ArghParser(version=pkg_resources.get_distribution("awstools").version)
-
-parser.add_argument('--config', default=None, help=HELP_CFG)
-parser.add_argument('--settings', default=None, help=HELP_SETTINGS)
-
-parser.add_commands([
-    cloudformation.ls,
-    cloudformation.create,
-    cloudformation.update,
-    cloudformation.batch_update,
-    cloudformation.delete,
-    cloudformation.info,
-    cloudformation.outputs,
-    cloudformation.resources,
-    cloudformation.events,
-    cloudformation.activities,
-])
-
-parser.dispatch(completion=False)

File setup.py

View file
         "argh",
         "PyYaml",
         "boto",
+        "arrow",
+        "prettytable",
     ],
     extras_require={ 'test': ['nose', 'nosexcover', 'coverage', 'mock'] },
     entry_points={
         'console_scripts': [
             'cfnas = awstools.commands.cfnautoscale:main',
+            'cfn = awstools.commands.cloudformation:main',
         ]
     },
     scripts=[
-        "scripts/cfn",
         "scripts/ec2ssh",
     ]
 )