Revision b0e7f310

b/snf-cyclades-app/synnefo/api/management/commands/flavor-list.py
1
# Copyright 2012 GRNET S.A. All rights reserved.
1
# Copyright 2012-2013 GRNET S.A. All rights reserved.
2 2
#
3 3
# Redistribution and use in source and binary forms, with or
4 4
# without modification, are permitted provided that the following
......
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
from optparse import make_option
34
from synnefo.webproject.management.commands import ListCommand
35
from synnefo.db.models import Flavor, VirtualMachine
35 36

  
36
from django.core.management.base import BaseCommand, CommandError
37
from synnefo.management.common import (format_bool, filter_results,
38
                                       pprint_table)
39 37

  
40
from synnefo.db.models import Flavor
38
class Command(ListCommand):
39
    help = "List available server flavors"
41 40

  
42
FIELDS = Flavor._meta.get_all_field_names()
41
    object_class = Flavor
42
    deleted_field = "deleted"
43 43

  
44
    def get_vms(flavor):
45
        return VirtualMachine.objects.filter(flavor=flavor, deleted=False)\
46
                                     .count()
44 47

  
45
class Command(BaseCommand):
46
    help = "List flavors"
48
    FIELDS = {
49
        "id": ("id", "Flavor's unique ID"),
50
        "name": ("name", "Flavor's unique name"),
51
        "cpu": ("cpu", "Number of CPUs"),
52
        "ram": ("ram", "Size(MB) of RAM"),
53
        "disk": ("disk", "Size(GB) of disk"),
54
        "template": ("disk_template", "Disk template"),
55
        "vms": (get_vms, "Number of active servers using this flavor")
56
    }
47 57

  
48
    option_list = BaseCommand.option_list + (
49
        make_option(
50
            '-c',
51
            action='store_true',
52
            dest='csv',
53
            default=False,
54
            help="Use pipes to separate values"),
55
        make_option(
56
            '--deleted',
57
            action='store_true',
58
            dest='deleted',
59
            default=False,
60
            help="Include deleted flavors"),
61
        make_option(
62
            '--filter-by',
63
            dest='filter_by',
64
            help="Filter results. Comma seperated list of key=val pairs"
65
                 " that displayed entries must satisfy. e.g."
66
                 " --filter-by \"cpu=1,ram!=1024\"."
67
                 "Available keys are: %s" % ", ".join(FIELDS))
68
    )
69

  
70
    def handle(self, *args, **options):
71
        if args:
72
            raise CommandError("Command doesn't accept any arguments")
73

  
74
        if options['deleted']:
75
            flavors = Flavor.objects.all()
76
        else:
77
            flavors = Flavor.objects.filter(deleted=False)
78

  
79
        filter_by = options['filter_by']
80
        if filter_by:
81
            flavors = filter_results(flavors, filter_by)
82

  
83
        headers = ('id', 'name', 'cpus', 'ram', 'disk', 'template', 'deleted')
84
        table = []
85
        for flavor in flavors.order_by('id'):
86
            id = str(flavor.id)
87
            cpu = str(flavor.cpu)
88
            ram = str(flavor.ram)
89
            disk = str(flavor.disk)
90
            deleted = format_bool(flavor.deleted)
91
            fields = (id, flavor.name, cpu, ram, disk, flavor.disk_template,
92
                      deleted)
93

  
94
            table.append(fields)
95

  
96
        separator = " | " if options['csv'] else None
97
        pprint_table(self.stdout, table, headers, separator)
58
    fields = ["id", "name", "cpu", "ram", "disk", "template", "vms"]
b/snf-cyclades-app/synnefo/api/management/commands/network-list.py
1
# Copyright 2012 GRNET S.A. All rights reserved.
1
# Copyright 2012-2013 GRNET S.A. All rights reserved.
2 2
#
3 3
# Redistribution and use in source and binary forms, with or
4 4
# without modification, are permitted provided that the following
......
33 33

  
34 34
from optparse import make_option
35 35

  
36
from django.core.management.base import BaseCommand, CommandError
37
from synnefo.management.common import (format_bool, filter_results, UserCache,
38
                                       Omit)
36
from synnefo.webproject.management.commands import ListCommand
39 37
from synnefo.db.models import Network
40
from synnefo.management.common import pprint_table
41 38

  
42
FIELDS = Network._meta.get_all_field_names()
39
from logging import getLogger
40
log = getLogger(__name__)
43 41

  
44 42

  
45
class Command(BaseCommand):
46
    help = "List networks"
47

  
48
    option_list = BaseCommand.option_list + (
49
        make_option(
50
            '-c',
51
            action='store_true',
52
            dest='csv',
53
            default=False,
54
            help="Use pipes to separate values"),
55
        make_option(
56
            '--deleted',
57
            action='store_true',
58
            dest='deleted',
59
            default=False,
60
            help="Include deleted networks"),
43
class Command(ListCommand):
44
    option_list = ListCommand.option_list + (
61 45
        make_option(
62 46
            '--public',
63 47
            action='store_true',
......
65 49
            default=False,
66 50
            help="List only public networks"),
67 51
        make_option(
68
            '--user',
69
            dest='user',
70
            help="List only networks of the specified user"
71
                 " (uuid or display name"),
72
        make_option('--ipv6',
52
            '--ipv6',
73 53
            action='store_true',
74 54
            dest='ipv6',
75 55
            default=False,
76
            help="Show IPv6 information of the network"),
77
        make_option(
78
            '--filter-by',
79
            dest='filter_by',
80
            help="Filter results. Comma seperated list of key 'cond' val pairs"
81
                 " that displayed entries must satisfy. e.g."
82
                 " --filter-by \"name=Network-1,link!=prv0\"."
83
                 " Available keys are: %s" % ", ".join(FIELDS)),
84
        make_option(
85
            '--displayname',
86
            action='store_true',
87
            dest='displayname',
88
            default=False,
89
            help="Display both uuid and display name"),
56
            help="Include IPv6 information"),
90 57
    )
91 58

  
92
    def handle(self, *args, **options):
93
        if args:
94
            raise CommandError("Command doesn't accept any arguments")
95

  
96
        ucache = UserCache()
97

  
98
        if options['deleted']:
99
            networks = Network.objects.all()
100
        else:
101
            networks = Network.objects.filter(deleted=False)
102

  
103
        if options['public']:
104
            networks = networks.filter(public=True)
105

  
106
        user = options['user']
107
        if user:
108
            if '@' in user:
109
                user = ucache.get_uuid(user)
110
            networks = networks.filter(userid=user)
111

  
112
        filter_by = options['filter_by']
113
        if filter_by:
114
            networks = filter_results(networks, filter_by)
115

  
116
        displayname = options['displayname']
117

  
118
        headers = filter(lambda x: x is not Omit,
119
                         ['id',
120
                          'name',
121
                          'flavor',
122
                          'owner_uuid',
123
                          'owner_name' if displayname else Omit,
124
                          'mac_prefix',
125
                          'dhcp',
126
                          'state',
127
                          'link',
128
                          'vms',
129
                          'public',
130
                          ])
131

  
132
        if options['ipv6']:
133
            headers.extend(['IPv6 Subnet', 'IPv6 Gateway'])
134
        else:
135
            headers.extend(['IPv4 Subnet', 'IPv4 Gateway'])
136

  
137
        if displayname:
138
            uuids = list(set([network.userid for network in networks]))
139
            ucache.fetch_names(uuids)
140

  
141
        table = []
142
        for network in networks.order_by("id"):
143
            uuid = network.userid
144
            if displayname:
145
                dname = ucache.get_name(uuid)
146

  
147
            fields = filter(lambda x: x is not Omit,
148
                            [str(network.id),
149
                             network.name,
150
                             network.flavor,
151
                             uuid or '-',
152
                             dname or '-' if displayname else Omit,
153
                             network.mac_prefix or '-',
154
                             str(network.dhcp),
155
                             network.state,
156
                             network.link or '-',
157
                             str(network.machines.count()),
158
                             format_bool(network.public),
159
                             ])
160

  
161
            if options['ipv6']:
162
                fields.extend([network.subnet6 or '', network.gateway6 or ''])
163
            else:
164
                fields.extend([network.subnet, network.gateway or ''])
165
            table.append(fields)
166

  
167
        separator = " | " if options['csv'] else None
168
        pprint_table(self.stdout, table, headers, separator)
59
    object_class = Network
60
    deleted_field = "deleted"
61
    user_uuid_field = "userid"
62

  
63
    def get_machines(network):
64
        return network.machines.filter(deleted=False).count()
65

  
66
    def get_backends(network):
67
        return network.backend_networks.values_list("backend_id", flat=True)
68

  
69
    FIELDS = {
70
        "id": ("id", "The ID of the network"),
71
        "name": ("name", "The name of the network"),
72
        "user.uuid": ("userid", "The UUID of the network's owner"),
73
        "public": ("public", "Whether network is public or private"),
74
        "flavor": ("flavor", "The network's flavor"),
75
        "state": ("state", "The network's state"),
76
        "dhcp": ("dhcp", "Whether network uses nfdhcpd or not"),
77
        "subnet.ipv4": ("subnet", "The IPv4 subnet of the network"),
78
        "gateway.ipv4": ("gateway", "The IPv4 gateway of the network"),
79
        "subnet.ipv6": ("subnet", "The IPv6 subnet of the network"),
80
        "gateway.ipv6": ("gateway", "The IPv6 gateway of the network"),
81
        "created": ("created", "The date the network was created"),
82
        "updated": ("created", "The date the network was updated"),
83
        "deleted": ("deleted", "Whether the network is deleted or not"),
84
        "mode": ("mode", "The mode of the network"),
85
        "link": ("link", "The link of the network"),
86
        "mac_prefix": ("mac_prefix", "The network's MAC prefix"),
87
        "vms": (get_machines, "Number of connected servers"),
88
        "backends": (get_backends, "IDs of Ganeti backends that the network is"
89
                                   " connected to"),
90
    }
91

  
92
    fields = ["id", "name", "user.uuid", "state", "public", "subnet.ipv4",
93
              "gateway.ipv4", "link", "mac_prefix"]
94

  
95
    def handle_args(self, *args, **options):
96
        if options["public"]:
97
            self.filters["public"] = True
98
        if options["ipv6"]:
99
            self.fields.extend(["subnet.ipv6", "gateway.ipv6"])
b/snf-cyclades-app/synnefo/api/management/commands/server-list.py
1
# Copyright 2012 GRNET S.A. All rights reserved.
1
# Copyright 2012-2013 GRNET S.A. All rights reserved.
2 2
#
3 3
# Redistribution and use in source and binary forms, with or
4 4
# without modification, are permitted provided that the following
......
33 33

  
34 34
from optparse import make_option
35 35

  
36
from django.core.management.base import BaseCommand, CommandError
37
from synnefo.management.common import (format_vm_state, get_backend, Omit,
38
                                       filter_results, pprint_table, UserCache)
39
from synnefo.api.util import get_image
36
from synnefo.webproject.management.commands import ListCommand
40 37
from synnefo.db.models import VirtualMachine
38
from synnefo.management.common import get_backend
39
from synnefo.api.util import get_image
41 40

  
42
import logging
43
log = logging.getLogger(__name__)
44

  
45
FIELDS = VirtualMachine._meta.get_all_field_names()
41
from logging import getLogger
42
log = getLogger(__name__)
46 43

  
47 44

  
48
class Command(BaseCommand):
45
class Command(ListCommand):
49 46
    help = "List servers"
50 47

  
51
    option_list = BaseCommand.option_list + (
52
        make_option(
53
            '-c',
54
            action='store_true',
55
            dest='csv',
56
            default=False,
57
            help="Use pipes to separate values"),
48
    option_list = ListCommand.option_list + (
58 49
        make_option(
59 50
            '--suspended',
60 51
            action='store_true',
......
62 53
            default=False,
63 54
            help="List only suspended servers"),
64 55
        make_option(
65
            '--build',
66
            action='store_true',
67
            dest='build',
68
            default=False,
69
            help="List only servers in the building state"),
70
        make_option(
71
            '--deleted',
72
            action='store_true',
73
            dest='deleted',
74
            default=False,
75
            help="Include deleted servers"),
76
        make_option(
77 56
            '--backend-id',
78 57
            dest='backend_id',
79 58
            help="List only servers of the specified backend"),
80 59
        make_option(
81
            '--user',
82
            dest='user',
83
            help="List only servers of the specified user (uuid or email)"),
84
        make_option(
85
            '--filter-by',
86
            dest='filter_by',
87
            help="Filter results. Comma seperated list of key `cond` val pairs"
88
                 " that displayed entries must satisfy. e.g."
89
                 " --filter-by \"operstate=STARTED,id>=22\"."
90
                 " Available keys are: %s" % ", ".join(FIELDS)),
60
            "--build",
61
            action="store_true",
62
            dest="build",
63
            default=False,
64
            help="List only servers in the building state"),
91 65
        make_option(
92
            '--displayname',
93
            action='store_true',
94
            dest='displayname',
66
            "--image-name",
67
            action="store_true",
68
            dest="image_name",
95 69
            default=False,
96
            help="Display both uuid and display name"),
70
            help="Display image name instead of image ID"),
97 71
    )
98 72

  
99
    def handle(self, *args, **options):
100
        if args:
101
            raise CommandError("Command doesn't accept any arguments")
73
    object_class = VirtualMachine
74
    deleted_field = "deleted"
75
    user_uuid_field = "userid"
102 76

  
103
        ucache = UserCache()
104

  
105
        if options['backend_id']:
106
            backend = get_backend(options['backend_id'])
107
            servers = backend.virtual_machines
108
        else:
109
            servers = VirtualMachine.objects
77
    def get_public_ip(vm):
78
        try:
79
            return vm.nics.all()[0].ipv4
80
        except IndexError:
81
            return None
110 82

  
111
        if options['deleted']:
112
            servers = servers.all()
83
    def format_vm_state(vm):
84
        if vm.operstate == "BUILD":
85
            return "BUILD(" + str(vm.buildpercentage) + "%)"
113 86
        else:
114
            servers = servers.filter(deleted=False)
115

  
116
        if options['suspended']:
117
            servers = servers.filter(suspended=True)
118

  
119
        if options['build']:
120
            servers = servers.filter(operstate='BUILD')
121

  
122
        user = options['user']
123
        if user:
124
            if '@' in user:
125
                user = ucache.get_uuid(user)
126
            servers = servers.filter(userid=user)
127

  
128
        filter_by = options['filter_by']
129
        if filter_by:
130
            servers = filter_results(servers, filter_by)
131

  
132
        displayname = options['displayname']
133

  
134
        cache = ImageCache()
135

  
136
        headers = filter(lambda x: x is not Omit,
137
                         ['id',
138
                          'name',
139
                          'owner_uuid',
140
                          'owner_name' if displayname else Omit,
141
                          'flavor',
142
                          'image',
143
                          'state',
144
                          'backend',
145
                          ])
146

  
147
        if displayname:
148
            uuids = list(set([server.userid for server in servers]))
149
            ucache.fetch_names(uuids)
150

  
151
        table = []
152
        for server in servers.order_by('id'):
153
            try:
154
                name = server.name.decode('utf8')
155
            except UnicodeEncodeError:
156
                name = server.name
157

  
158
            flavor = server.flavor.name
159

  
160
            image = cache.get_image(server.imageid, server.userid)
161

  
162
            state = format_vm_state(server)
163

  
164
            uuid = server.userid
165
            if displayname:
166
                dname = ucache.get_name(server.userid)
167

  
168
            fields = filter(lambda x: x is not Omit,
169
                            [str(server.id),
170
                             name,
171
                             uuid,
172
                             dname if displayname else Omit,
173
                             flavor,
174
                             image,
175
                             state,
176
                             str(server.backend),
177
                             ])
178
            table.append(fields)
179

  
180
        separator = " | " if options['csv'] else None
181
        pprint_table(self.stdout, table, headers, separator)
87
            return vm.operstate
88

  
89
    FIELDS = {
90
        "id": ("id", "ID of the server"),
91
        "name": ("name", "Name of the server"),
92
        "user.uuid": ("userid", "The UUID of the server's owner"),
93
        "flavor": ("flavor.name", "The name of the server's flavor"),
94
        "backend": ("backend", "The Ganeti backend that hosts the VM"),
95
        "image.id": ("imageid", "The ID of the server's image"),
96
        "image.name": ("image", "The name of the server's image"),
97
        "state": (format_vm_state, "The current state of the server"),
98
        "ip": (get_public_ip, "The public IP of the server"),
99
        "created": ("created", "The date the server was created"),
100
        "deleted": ("deleted", "Whether the server is deleted or not"),
101
        "suspended": ("suspended", "Whether the server is administratively"
102
                      " suspended"),
103
    }
104

  
105
    fields = ["id", "name", "user.uuid", "state", "flavor", "image.id",
106
              "backend"]
107

  
108
    def handle_args(self, *args, **options):
109
        if options["suspended"]:
110
            self.filters["suspended"] = True
111

  
112
        if options["backend_id"]:
113
            backend = get_backend(options["backend_id"])
114
            self.filters["backend"] = backend.id
115

  
116
        if options["build"]:
117
            self.filters["operstate"] = "BUILD"
118

  
119
        if options["image_name"]:
120
            self.fields.replace("image.id", "image.name")
121

  
122
    def handle_db_objects(self, rows, *args, **kwargs):
123
        icache = ImageCache()
124
        for vm in rows:
125
            vm.image = icache.get_image(vm.imageid, vm.userid)
182 126

  
183 127

  
184 128
class ImageCache(object):
b/snf-cyclades-app/synnefo/logic/management/commands/backend-list.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
from optparse import make_option
35
from django.core.management.base import BaseCommand, CommandError
36
from synnefo.management.common import pprint_table
34
from synnefo.db.models import Backend, IPPoolTable
35
from synnefo.webproject.management.commands import ListCommand
37 36

  
38
from synnefo.db.models import Backend
39 37

  
38
class Command(ListCommand):
39
    help = "List Ganeti backends"
40
    object_class = Backend
40 41

  
41
class Command(BaseCommand):
42
    help = "List backends"
42
    def get_vms(backend):
43
        return backend.virtual_machines.filter(deleted=False).count()
43 44

  
44
    option_list = BaseCommand.option_list + (
45
        make_option('-c',
46
                    action='store_true',
47
                    dest='csv',
48
                    default=False,
49
                    help="Use pipes to separate values"),
50
    )
45
    def get_mem(backend):
46
        return "%s/%s" % (backend.mfree, backend.mtotal)
51 47

  
52
    def handle(self, *args, **options):
53
        if args:
54
            raise CommandError("Command doesn't accept any arguments")
48
    def get_disk(backend):
49
        return "%s/%s" % (backend.dfree, backend.dtotal)
55 50

  
56
        backends = Backend.objects.order_by('id')
51
    def get_ips(backend):
52
        free_ips = 0
53
        total_ips = 0
54
        for bnet in backend.networks.filter(deleted=False,
55
                                            network__public=True,
56
                                            network__deleted=False):
57
            network = bnet.network
58
            try:
59
                pool = IPPoolTable.objects.get(id=network.pool_id).pool
60
                free_ips += pool.count_available()
61
                total_ips += pool.pool_size
62
            except IPPoolTable.DoesNotExist:
63
                pass
64
        return "%s/%s" % (free_ips, total_ips)
57 65

  
58
        headers = ('id', 'clustername', 'port', 'username', "VMs", 'drained',
59
                   'offline')
60
        table = []
61
        for backend in backends:
62
            id = str(backend.id)
63
            vms = str(backend.virtual_machines.filter(deleted=False).count())
64
            fields = (id, backend.clustername, str(backend.port),
65
                      backend.username, vms, str(backend.drained),
66
                      str(backend.offline))
67
            table.append(fields)
66
    FIELDS = {
67
        "id": ("id", "Backend's unique ID"),
68
        "clustername": ("clustername", "The name of the Ganeti cluster"),
69
        "port": ("port", ""),
70
        "username": ("username", "The RAPI user"),
71
        "drained": ("drained", "Whether backend is marked as drained"),
72
        "offline": ("offline", "Whether backend if marked as offline"),
73
        "vms": (get_vms, "Number of VMs that this backend hosts"),
74
        "ips": (get_ips, "free/total number of public IPs"),
75
        "mem": (get_mem, "free/total memory (MB)"),
76
        "disk": (get_mem, "free/total disk (GB)"),
77
    }
68 78

  
69
        separator = " | " if options['csv'] else None
70
        pprint_table(self.stdout, table, headers, separator)
79
    fields = ["id", "clustername", "port", "username", "drained", "offline",
80
              "vms", "ips"]
b/snf-webproject/synnefo/webproject/management/commands/__init__.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
from optparse import make_option
35

  
36
from django.core.management.base import BaseCommand, CommandError
37
from django.core.exceptions import FieldError
38

  
39
from synnefo.webproject.management import util
40
from synnefo.management.common import UserCache
41

  
42

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

  
46
    Management command to handle common tasks when implementing a -list
47
    management command. This class handles the following tasks:
48

  
49
    * Retrieving objects from database.
50

  
51
    The DB model class is declared in ``object_class`` class attribute. Also,
52
    results can be filter using either the ``filters`` and ``excludes``
53
    attribute or the "--filter-by" option.
54

  
55
    * Display specific fields of the database objects.
56

  
57
    List of available fields is defined in the ``FIELDS`` class attribute,
58
    which is a dictionary mapping from field names to tuples containing the
59
    way the field is retrieved and a text help message to display. The first
60
    field of the tuple is either a string containing a chain of attribute
61
    accesses (e.g. "machine.flavor.cpu") either a callable function, taking
62
    as argument the DB object and returning a single value.
63

  
64
    The fields that will be displayed be default is contained in the ``fields``
65
    class attribute. The user can specify different fields using the "--fields"
66
    option.
67

  
68
    * Handling of user UUIDs and names.
69

  
70
    If the ``user_uuid_field`` is declared, then "--user" and "--display-mails"
71
    options will become available. The first one allows filtering via either
72
    a user's UUID or display name. The "--displayname" option will append
73
    the displayname of ther user with "user_uuid_field" to the output.
74

  
75
    * Pretty printing output to a nice table.
76

  
77
    """
78

  
79
    # The following fields must be handled in the ListCommand subclasses!
80

  
81
    # The django DB model
82
    object_class = None
83
    # The name of the field containg the user ID of the user, if any.
84
    user_uuid_field = None
85
    # The name of the field containg the deleted flag, if any.
86
    deleted_field = None
87
    # Dictionary with all available fields
88
    FIELDS = {}
89
    # List of fields to display by default
90
    fields = []
91
    # Default filters and excludes
92
    filters = {}
93
    excludes = {}
94

  
95
    help = "Generic List Command"
96
    option_list = BaseCommand.option_list + (
97
        make_option(
98
            "-o", "--output",
99
            dest="fields",
100
            help="Comma-separated list of output fields"),
101
        make_option(
102
            "--list-fields",
103
            dest="list_fields",
104
            action="store_true",
105
            default=False,
106
            help="List available output fields"),
107
        make_option(
108
            "--filter-by",
109
            dest="filter_by",
110
            metavar="FILTERS",
111
            help="Filter results. Comma separated list of key `cond` val pairs"
112
                 " that displayed entries must satisfy. e.g."
113
                 " --filter-by \"deleted=False,id>=22\"."),
114
        make_option(
115
            "--list-filters",
116
            dest="list_filters",
117
            action="store_true",
118
            default=False,
119
            help="List available filters"),
120
        make_option(
121
            "--no-headers",
122
            dest="headers",
123
            action="store_false",
124
            default=True,
125
            help="Do not display headers"),
126
        make_option(
127
            "--output-format",
128
            dest="output_format",
129
            metavar="[pretty, csv, json]",
130
            default="pretty",
131
            choices=["pretty", "csv", "json"],
132
            help="Select the output format: pretty [the default], tabs"
133
                 " [tab-separated output], csv [comma-separated output]"),
134
    )
135

  
136
    def __init__(self, *args, **kwargs):
137
        if self.user_uuid_field:
138
            self.option_list += (
139
                make_option(
140
                    "-u", "--user",
141
                    dest="user",
142
                    metavar="USER",
143
                    help="List items only for this user."
144
                         " 'USER' can be either a user UUID or a display"
145
                         " name"),
146
                make_option(
147
                    "--display-mails",
148
                    dest="display_mails",
149
                    action="store_true",
150
                    default=False,
151
                    help="Include the user's email"),
152
            )
153

  
154
        if self.deleted_field:
155
            self.option_list += (
156
                make_option(
157
                    "-d", "--deleted",
158
                    dest="deleted",
159
                    action="store_true",
160
                    help="Display only deleted items"),
161
            )
162
        super(ListCommand, self).__init__(*args, **kwargs)
163

  
164
    def handle(self, *args, **options):
165
        if len(args) > 0:
166
            raise CommandError("List commands do not accept any argument")
167

  
168
        assert(self.object_class), "object_class variable must be declared"
169

  
170
        if options["list_fields"]:
171
            self.display_fields()
172
            return
173

  
174
        if options["list_filters"]:
175
            self.display_filters()
176
            return
177

  
178
        # --output option
179
        if options["fields"]:
180
            fields = options["fields"]
181
            fields = fields.split(",")
182
            self.validate_fields(fields)
183
            self.fields = options["fields"].split(",")
184

  
185
        # --filter-by option
186
        if options["filter_by"]:
187
            filters, excludes = util.parse_filters(options["filter_by"])
188
        else:
189
            filters, excludes = ({}, {})
190

  
191
        self.filters.update(filters)
192
        self.excludes.update(excludes)
193

  
194
        # --user option
195
        user = options.get("user")
196
        if user:
197
            if "@" in user:
198
                user = UserCache().get_uuid(user)
199
            self.filters[self.user_uuid_field] = user
200

  
201
        # --deleted option
202
        if self.deleted_field:
203
            deleted = options.get("deleted")
204
            if deleted:
205
                self.filters[self.deleted_field] = True
206
            else:
207
                self.filters[self.deleted_field] = False
208

  
209
        # Special handling of arguments
210
        self.handle_args(self, *args, **options)
211

  
212
        objects = self.object_class.objects
213
        try:
214
            objects = objects.filter(**self.filters)
215
            objects = objects.exclude(**self.excludes)
216
        except FieldError as e:
217
            raise CommandError(e)
218
        except Exception as e:
219
            raise CommandError("Can not filter results: %s" % e)
220

  
221
        # --display-mails option
222
        display_mails = options.get("display_mails")
223
        if display_mails:
224
            if 'user_mail' in self.object_class._meta.get_all_field_names():
225
                raise RuntimeError("%s has already a 'user_mail' attribute")
226

  
227
            self.fields.append("user.email")
228
            self.FIELDS["user.email"] =\
229
                ("user_email", "The email of the owner.")
230
            uuids = [getattr(obj, self.user_uuid_field) for obj in objects]
231
            ucache = UserCache()
232
            ucache.fetch_names(list(set(uuids)))
233
            for obj in objects:
234
                uuid = getattr(obj, self.user_uuid_field)
235
                obj.user_email = ucache.get_name(uuid)
236

  
237
        # Special handling of DB results
238
        self.handle_db_objects(objects)
239

  
240
        headers = self.fields
241
        columns = [self.FIELDS[key][0] for key in headers]
242

  
243
        table = []
244
        for obj in objects:
245
            row = []
246
            for attr in columns:
247
                if callable(attr):
248
                    row.append(attr(obj))
249
                else:
250
                    item = obj
251
                    attrs = attr.split(".")
252
                    for attr in attrs:
253
                        item = getattr(item, attr)
254
                    row.append(item)
255
            table.append(row)
256

  
257
        # Special handle of output
258
        self.handle_output(table, headers)
259

  
260
        # Print output
261
        output_format = options["output_format"]
262
        headers = headers if options["headers"] else None
263
        util.pprint_table(self.stdout, table, headers, output_format)
264

  
265
    def handle_args(self, *args, **kwargs):
266
        pass
267

  
268
    def handle_db_objects(self, objects):
269
        pass
270

  
271
    def handle_output(self, table, headers):
272
        pass
273

  
274
    def display_fields(self):
275
        headers = ["Field", "Description"]
276
        table = []
277
        for field, (_, help_msg) in self.FIELDS.items():
278
            table.append((field, help_msg))
279
        util.pprint_table(self.stdout, table, headers)
280

  
281
    def validate_fields(self, fields):
282
        for f in fields:
283
            if f not in self.FIELDS.keys():
284
                raise CommandError("Unknown field '%s'. 'Use --list-fields"
285
                                   " option to find out available fields."
286
                                   % f)
287

  
288
    def display_filters(self):
289
        headers = ["Filter", "Description", "Help"]
290
        table = []
291
        for field in self.object_class._meta.fields:
292
            table.append((field.name, field.verbose_name, field.help_text))
293
        util.pprint_table(self.stdout, table, headers)
b/snf-webproject/synnefo/webproject/management/util.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
import json
34 35
from datetime import datetime
35 36
from django.utils.timesince import timesince, timeuntil
36 37

  
......
113 114
    return (filter_dict, exclude_dict)
114 115

  
115 116

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

  
119 121
    Works by finding out the max width of each column and padding to data
......
121 123
    """
122 124

  
123 125
    assert(isinstance(table, (list, tuple))), "Invalid table type"
124
    sep = separator if separator else "  "
125

  
126 126
    if headers:
127 127
        assert(isinstance(headers, (list, tuple))), "Invalid headers type"
128
        table.insert(0, headers)
129 128

  
130
    def strignify(obj):
129
    sep = separator if separator else "  "
130

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

  
136
    table = [map(strignify, row) for row in table]
137

  
138
    # Find out the max width of each column
139
    widths = [max(map(len, col)) for col in zip(*table)]
140

  
141
    t_length = sum(widths) + len(sep) * (len(widths) - 1)
142
    if headers:
143
        # pretty print the headers
144
        line = sep.join(uenc(v.rjust(w)) for v, w in zip(headers, widths))
145
        out.write(line + "\n")
146
        out.write("-" * t_length + "\n")
147
        # remove headers
148
        table = table[1:]
149

  
150
    # print the rest table
151
    for row in table:
152
        line = sep.join(uenc(v.rjust(w)) for v, w in zip(row, widths))
153
        out.write(line + "\n")
137
    headers = map(stringnify, headers)
138
    table = [map(stringnify, row) for row in table]
139

  
140
    if output_format == "json":
141
        table = [dict(zip(headers, row)) for row in table]
142
        out.write(json.dumps(table, indent=4))
143
        out.write("\n")
144
    elif output_format == "csv":
145
        if headers:
146
            line = ",".join("\"%s\"" % uenc(v) for v in headers)
147
            out.write(line + "\n")
148
            for row in table:
149
                line = ",".join("\"%s\"" % uenc(v) for v in row)
150
                out.write(line + "\n")
151
    elif output_format == "pretty":
152
        # Find out the max width of each column
153
        widths = [max(map(len, col)) for col in zip(*([headers] + table))]
154

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

  
162
        # print the rest table
163
        for row in table:
164
            line = sep.join(uenc(v.rjust(w)) for v, w in zip(row, widths))
165
            out.write(line + "\n")
166
    else:
167
        raise ValueError("Unknown output format '%s'" % output_format)

Also available in: Unified diff