Revision 28578e52

b/snf-cyclades-app/synnefo/api/management/commands/server-show.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
from django.core.management.base import BaseCommand, CommandError
35
from synnefo.webproject.management.util import format_bool, format_date
34
from django.core.management.base import CommandError
35
from synnefo.webproject.management.commands import SynnefoCommand
36 36
from synnefo.management.common import (format_vm_state, get_vm,
37 37
                                       get_image)
38 38
from synnefo.lib.astakos import UserCache
39 39
from synnefo.settings import (CYCLADES_ASTAKOS_SERVICE_TOKEN as ASTAKOS_TOKEN,
40 40
                              ASTAKOS_URL)
41
from synnefo.webproject.management import utils
41 42

  
42 43

  
43
class Command(BaseCommand):
44
class Command(SynnefoCommand):
44 45
    args = "<server ID>"
45 46
    help = "Show server info"
46 47

  
......
61 62
        image = '%s (%s)' % (imageid, image_name)
62 63

  
63 64
        kv = {
64
            'id': server.id,
65
            'name': server.name,
66
            'owner_uuid': userid,
67
            'owner_name': UserCache(ASTAKOS_URL, ASTAKOS_TOKEN).get_name(userid),
68
            'created': format_date(server.created),
69
            'updated': format_date(server.updated),
70
            'image': image,
71
            'host id': server.hostid,
72
            'flavor': flavor,
73
            'deleted': format_bool(server.deleted),
74
            'suspended': format_bool(server.suspended),
75
            'state': format_vm_state(server),
65
          'id': server.id,
66
          'name': server.name,
67
          'owner_uuid': userid,
68
          'owner_name': UserCache(ASTAKOS_URL, ASTAKOS_TOKEN).get_name(userid),
69
          'created': utils.format_date(server.created),
70
          'updated': utils.format_date(server.updated),
71
          'image': image,
72
          'host id': server.hostid,
73
          'flavor': flavor,
74
          'deleted': utils.format_bool(server.deleted),
75
          'suspended': utils.format_bool(server.suspended),
76
          'state': format_vm_state(server),
76 77
        }
77 78

  
78
        for key, val in sorted(kv.items()):
79
            line = '%s: %s\n' % (key.rjust(16), val)
80
            self.stdout.write(line.encode('utf8'))
79
        utils.pprint_table(self.stdout, [kv.values()], kv.keys(),
80
                           options["output_format"], vertical=True)
b/snf-cyclades-app/synnefo/logic/management/commands/backend-add.py
39 39
                                   create_network_synced,
40 40
                                   connect_network_synced)
41 41
from synnefo.management.common import check_backend_credentials
42
from synnefo.webproject.management.util import pprint_table
42
from synnefo.webproject.management.utils import pprint_table
43 43

  
44 44

  
45 45
class Command(BaseCommand):
b/snf-cyclades-app/synnefo/logic/management/commands/backend-modify.py
33 33

  
34 34
from optparse import make_option
35 35
from django.core.management.base import BaseCommand, CommandError
36
from synnefo.webproject.management.util import parse_bool
36
from synnefo.webproject.management.utils import parse_bool
37 37
from synnefo.management.common import (get_backend, check_backend_credentials)
38 38

  
39 39

  
b/snf-webproject/synnefo/webproject/management/commands/__init__.py
36 36
from django.core.management.base import BaseCommand, CommandError
37 37
from django.core.exceptions import FieldError
38 38

  
39
from synnefo.webproject.management import util
39
from synnefo.webproject.management import utils
40 40
from synnefo.lib.astakos import UserCache
41 41

  
42 42

  
43
class SynnefoCommand(BaseCommand):
44
    option_list = BaseCommand.option_list + (
45
        make_option(
46
            "--output-format",
47
            dest="output_format",
48
            metavar="[pretty, csv, json]",
49
            default="pretty",
50
            choices=["pretty", "csv", "json"],
51
            help="Select the output format: pretty [the default], tabs"
52
                 " [tab-separated output], csv [comma-separated output]"),
53
    )
54

  
55

  
43 56
class ListCommand(BaseCommand):
44 57
    """Generic *-list management command.
45 58

  
......
192 205

  
193 206
        # --filter-by option
194 207
        if options["filter_by"]:
195
            filters, excludes = util.parse_filters(options["filter_by"])
208
            filters, excludes = utils.parse_filters(options["filter_by"])
196 209
        else:
197 210
            filters, excludes = ({}, {})
198 211

  
......
270 283
        output_format = options["output_format"]
271 284
        if output_format != "json" and not options["headers"]:
272 285
            headers = None
273
        util.pprint_table(self.stdout, table, headers, output_format)
286
        utils.pprint_table(self.stdout, table, headers, output_format)
274 287

  
275 288
    def handle_args(self, *args, **kwargs):
276 289
        pass
......
286 299
        table = []
287 300
        for field, (_, help_msg) in self.FIELDS.items():
288 301
            table.append((field, help_msg))
289
        util.pprint_table(self.stdout, table, headers)
302
        utils.pprint_table(self.stdout, table, headers)
290 303

  
291 304
    def validate_fields(self, fields):
292 305
        for f in fields:
......
300 313
        table = []
301 314
        for field in self.object_class._meta.fields:
302 315
            table.append((field.name, field.verbose_name, field.help_text))
303
        util.pprint_table(self.stdout, table, headers)
316
        utils.pprint_table(self.stdout, table, headers)
/dev/null
1
# Copyright 2012 - 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
import json
35
import csv
36
import functools
37
from datetime import datetime
38
from django.utils.timesince import timesince, timeuntil
39

  
40
from synnefo.util.text import uenc, udec
41

  
42

  
43
def parse_bool(value, strict=True):
44
    """Convert a string to boolen value.
45

  
46
    If string is True, then ValueError will be raised, if the string can not be
47
    converted to boolean. Otherwise the string will be returned as is.
48

  
49
    """
50
    if value.lower() in ("yes", "true", "t", "1"):
51
        return True
52
    if value.lower() in ("no", "false", "f", "0"):
53
        return False
54

  
55
    if strict:
56
        raise ValueError("Can convert '%s' to boolean value")
57
    else:
58
        return value
59

  
60

  
61
def format_bool(b):
62
    """Convert a boolean value to YES or NO."""
63
    return "YES" if b else "NO"
64

  
65

  
66
def format_date(d):
67
    if not d:
68
        return ""
69

  
70
    if d < datetime.now():
71
        return timesince(d) + " ago"
72
    else:
73
        return "in " + timeuntil(d)
74

  
75

  
76
def parse_filters(filter_by):
77
    """Parse a string into lookup parameters for QuerySet.filter(**kwargs).
78

  
79
    This functions converts a string of comma-separated key 'cond' val triples
80
    to two dictionaries, containing lookup parameters to be used for filter
81
    and exclude functions of QuerySet.
82

  
83
    e.g. filter_by="foo>=2, baz!=4" -> ({"foo__gte": "2"}, {"baz": "4"})
84

  
85
    """
86

  
87
    filter_dict = {}
88
    exclude_dict = {}
89

  
90
    filter_list = filter_by.split(",")
91

  
92
    def map_field_type(query):
93
        if "!=" in query:
94
            key, val = query.split("!=")
95
            exclude_dict[key] = parse_bool(val, strict=False)
96
            return
97

  
98
        OP_MAP = {
99
            ">=": "__gte",
100
            "=>": "__gte",
101
            ">":  "__gt",
102
            "<=": "__lte",
103
            "=<": "__lte",
104
            "<":  "__lt",
105
            "=":  "",
106
        }
107

  
108
        for op, new_op in OP_MAP.items():
109
            if op in query:
110
                key, val = query.split(op)
111
                filter_dict[key + new_op] = parse_bool(val, strict=False)
112
                return
113

  
114
    map(lambda x: map_field_type(x), filter_list)
115

  
116
    return (filter_dict, exclude_dict)
117

  
118

  
119
def pprint_table(out, table, headers=None, output_format='pretty',
120
                 separator=None):
121
    """Print a pretty, aligned string representation of table.
122

  
123
    Works by finding out the max width of each column and padding to data
124
    to this value.
125
    """
126

  
127
    assert(isinstance(table, (list, tuple))), "Invalid table type"
128
    if headers:
129
        assert(isinstance(headers, (list, tuple))), "Invalid headers type"
130

  
131
    sep = separator if separator else "  "
132

  
133
    def stringnify(obj):
134
        if isinstance(obj, (unicode, str)):
135
            return udec(obj)
136
        else:
137
            return str(obj)
138

  
139
    if headers:
140
        headers = map(stringnify, headers)
141
    table = [map(stringnify, row) for row in table]
142

  
143
    if output_format == "json":
144
        assert(headers is not None), "json output format requires headers"
145
        table = [dict(zip(headers, row)) for row in table]
146
        out.write(json.dumps(table, indent=4))
147
        out.write("\n")
148
    elif output_format == "csv":
149
        cw = csv.writer(out)
150
        if headers:
151
            table.insert(0, headers)
152
        table = map(functools.partial(map, uenc), table)
153
        cw.writerows(table)
154
    elif output_format == "pretty":
155
        # Find out the max width of each column
156
        columns = [headers] + table if headers else table
157
        widths = [max(map(len, col)) for col in zip(*(columns))]
158

  
159
        t_length = sum(widths) + len(sep) * (len(widths) - 1)
160
        if headers:
161
            # pretty print the headers
162
            line = sep.join(uenc(v.rjust(w)) for v, w in zip(headers, widths))
163
            out.write(line + "\n")
164
            out.write("-" * t_length + "\n")
165

  
166
        # print the rest table
167
        for row in table:
168
            line = sep.join(uenc(v.rjust(w)) for v, w in zip(row, widths))
169
            out.write(line + "\n")
170
    else:
171
        raise ValueError("Unknown output format '%s'" % output_format)
b/snf-webproject/synnefo/webproject/management/utils.py
1
# Copyright 2012 - 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
import json
35
import csv
36
import functools
37
from datetime import datetime
38
from django.utils.timesince import timesince, timeuntil
39

  
40
from synnefo.util.text import uenc, udec
41

  
42

  
43
def parse_bool(value, strict=True):
44
    """Convert a string to boolen value.
45

  
46
    If string is True, then ValueError will be raised, if the string can not be
47
    converted to boolean. Otherwise the string will be returned as is.
48

  
49
    """
50
    if value.lower() in ("yes", "true", "t", "1"):
51
        return True
52
    if value.lower() in ("no", "false", "f", "0"):
53
        return False
54

  
55
    if strict:
56
        raise ValueError("Can convert '%s' to boolean value")
57
    else:
58
        return value
59

  
60

  
61
def format_bool(b):
62
    """Convert a boolean value to YES or NO."""
63
    return "YES" if b else "NO"
64

  
65

  
66
def format_date(d):
67
    if not d:
68
        return ""
69

  
70
    if d < datetime.now():
71
        return timesince(d) + " ago"
72
    else:
73
        return "in " + timeuntil(d)
74

  
75

  
76
def parse_filters(filter_by):
77
    """Parse a string into lookup parameters for QuerySet.filter(**kwargs).
78

  
79
    This functions converts a string of comma-separated key 'cond' val triples
80
    to two dictionaries, containing lookup parameters to be used for filter
81
    and exclude functions of QuerySet.
82

  
83
    e.g. filter_by="foo>=2, baz!=4" -> ({"foo__gte": "2"}, {"baz": "4"})
84

  
85
    """
86

  
87
    filter_dict = {}
88
    exclude_dict = {}
89

  
90
    filter_list = filter_by.split(",")
91

  
92
    def map_field_type(query):
93
        if "!=" in query:
94
            key, val = query.split("!=")
95
            exclude_dict[key] = parse_bool(val, strict=False)
96
            return
97

  
98
        OP_MAP = {
99
            ">=": "__gte",
100
            "=>": "__gte",
101
            ">":  "__gt",
102
            "<=": "__lte",
103
            "=<": "__lte",
104
            "<":  "__lt",
105
            "=":  "",
106
        }
107

  
108
        for op, new_op in OP_MAP.items():
109
            if op in query:
110
                key, val = query.split(op)
111
                filter_dict[key + new_op] = parse_bool(val, strict=False)
112
                return
113

  
114
    map(lambda x: map_field_type(x), filter_list)
115

  
116
    return (filter_dict, exclude_dict)
117

  
118

  
119
def pprint_table(out, table, headers=None, output_format='pretty',
120
                 separator=None, vertical=False):
121
    """Print a pretty, aligned string representation of table.
122

  
123
    Works by finding out the max width of each column and padding to data
124
    to this value.
125
    """
126

  
127
    assert(isinstance(table, (list, tuple))), "Invalid table type"
128
    if headers:
129
        assert(isinstance(headers, (list, tuple))), "Invalid headers type"
130

  
131
    sep = separator if separator else "  "
132

  
133
    def stringnify(obj):
134
        if isinstance(obj, (unicode, str)):
135
            return udec(obj)
136
        else:
137
            return str(obj)
138

  
139
    if headers:
140
        headers = map(stringnify, headers)
141
    table = [map(stringnify, row) for row in table]
142

  
143
    if output_format == "json":
144
        assert(headers is not None), "json output format requires headers"
145
        table = [dict(zip(headers, row)) for row in table]
146
        out.write(json.dumps(table, indent=4))
147
        out.write("\n")
148
    elif output_format == "csv":
149
        cw = csv.writer(out)
150
        if headers:
151
            table.insert(0, headers)
152
        table = map(functools.partial(map, uenc), table)
153
        cw.writerows(table)
154
    elif output_format == "pretty":
155
        if vertical:
156
            assert(len(table) == 1)
157
            row = table[0]
158
            max_key = max(map(len, headers))
159
            max_val = max(map(len, row))
160
            for row in table:
161
                for (k, v) in zip(headers, row):
162
                    k = uenc(k.ljust(max_key))
163
                    v = uenc(v.ljust(max_val))
164
                    out.write("%s: %s\n" % (k, v))
165
        else:
166
            # Find out the max width of each column
167
            columns = [headers] + table if headers else table
168
            widths = [max(map(len, col)) for col in zip(*(columns))]
169

  
170
            t_length = sum(widths) + len(sep) * (len(widths) - 1)
171
            if headers:
172
                # pretty print the headers
173
                line = sep.join(uenc(v.rjust(w))\
174
                                for v, w in zip(headers, widths))
175
                out.write(line + "\n")
176
                out.write("-" * t_length + "\n")
177

  
178
            # print the rest table
179
            for row in table:
180
                line = sep.join(uenc(v.rjust(w)) for v, w in zip(row, widths))
181
                out.write(line + "\n")
182
    else:
183
        raise ValueError("Unknown output format '%s'" % output_format)

Also available in: Unified diff