Passed
Push — master ( 901c2b...6be349 )
by Humberto
02:14
created

kytos.cli.commands.napps.api   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 306
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 204
dl 0
loc 306
rs 6.96
c 0
b 0
f 0
ccs 0
cts 184
cp 0
wmc 53

18 Methods

Rating   Name   Duplication   Size   Complexity  
A NAppsAPI.install() 0 4 1
A NAppsAPI.delete() 0 16 4
A NAppsAPI.enable_napp() 0 15 4
A NAppsAPI.disable_napp() 0 9 2
A NAppsAPI.prepare() 0 5 1
A NAppsAPI._print_napps() 0 14 4
A NAppsAPI.search() 0 15 2
A NAppsAPI.list() 0 22 3
A NAppsAPI.enable() 0 11 2
A NAppsAPI.print_napps() 0 24 4
A NAppsAPI.install_napp() 0 21 4
A NAppsAPI.reload() 0 18 4
A NAppsAPI.upload() 0 10 2
A NAppsAPI.enable_napps() 0 12 2
B NAppsAPI.install_napps() 0 32 6
A NAppsAPI.uninstall() 0 19 4
A NAppsAPI.disable() 0 14 3
A NAppsAPI.create() 0 4 1

How to fix   Complexity   

Complexity

Complex classes like kytos.cli.commands.napps.api often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Translate cli commands to non-cli code."""
2
import json
3
import logging
4
import os
5
import re
6
from urllib.error import HTTPError, URLError
7
8
import requests
9
10
from kytos.utils.exceptions import KytosException
11
from kytos.utils.napps import NAppsManager
12
13
LOG = logging.getLogger(__name__)
14
15
16
class NAppsAPI:
17
    """An API for the command-line interface.
18
19
    Use the config file only for required options. Static methods are called
20
    by the parser and they instantiate an object of this class to fulfill the
21
    request.
22
    """
23
24
    @classmethod
25
    def disable(cls, args):
26
        """Disable subcommand."""
27
        mgr = NAppsManager()
28
29
        if args['all']:
30
            napps = mgr.get_enabled()
31
        else:
32
            napps = args['<napp>']
33
34
        for napp in napps:
35
            mgr.set_napp(*napp)
36
            LOG.info('NApp %s:', mgr.napp_id)
37
            cls.disable_napp(mgr)
38
39
    @staticmethod
40
    def disable_napp(mgr):
41
        """Disable a NApp."""
42
        if mgr.is_enabled():
43
            LOG.info('  Disabling...')
44
            mgr.disable()
45
            LOG.info('  Disabled.')
46
        else:
47
            LOG.error("  NApp isn't enabled.")
48
49
    @classmethod
50
    def enable(cls, args):
51
        """Enable subcommand."""
52
        mgr = NAppsManager()
53
54
        if args['all']:
55
            napps = mgr.get_disabled()
56
        else:
57
            napps = args['<napp>']
58
59
        cls.enable_napps(napps)
60
61
    @classmethod
62
    def enable_napp(cls, mgr):
63
        """Install one NApp using NAppManager object."""
64
        try:
65
            if not mgr.is_enabled():
66
                LOG.info('    Enabling...')
67
                mgr.enable()
68
69
            # Check if NApp is enabled
70
            if mgr.is_enabled():
71
                LOG.info('    Enabled.')
72
            else:
73
                LOG.error('    Error enabling NApp.')
74
        except (FileNotFoundError, PermissionError) as exception:
75
            LOG.error('  %s', exception)
76
77
    @classmethod
78
    def enable_napps(cls, napps):
79
        """Enable a list of NApps.
80
81
        Args:
82
            napps (list): List of NApps.
83
        """
84
        mgr = NAppsManager()
85
        for napp in napps:
86
            mgr.set_napp(*napp)
87
            LOG.info('NApp %s:', mgr.napp_id)
88
            cls.enable_napp(mgr)
89
90
    @classmethod
91
    def create(cls, args):  # pylint: disable=unused-argument
92
        """Bootstrap a basic NApp structure on the current folder."""
93
        NAppsManager.create_napp(meta_package=args.get('--meta', False))
94
95
    @classmethod
96
    def upload(cls, args):  # pylint: disable=unused-argument
97
        """Upload the NApp to the NApps server.
98
99
        Create the NApp package and upload it to the NApp server.
100
        """
101
        try:
102
            NAppsManager().upload()
103
        except FileNotFoundError as err:
104
            LOG.error("Couldn't find %s in current directory.", err.filename)
105
106
    @classmethod
107
    def uninstall(cls, args):
108
        """Uninstall and delete NApps.
109
110
        For local installations, do not delete code outside install_path and
111
        enabled_path.
112
        """
113
        mgr = NAppsManager()
114
        for napp in args['<napp>']:
115
            mgr.set_napp(*napp)
116
            LOG.info('NApp %s:', mgr.napp_id)
117
            if mgr.is_installed():
118
                if mgr.is_enabled():
119
                    cls.disable_napp(mgr)
120
                LOG.info('  Uninstalling...')
121
                mgr.remote_uninstall()
122
                LOG.info('  Uninstalled.')
123
            else:
124
                LOG.error("  NApp isn't installed.")
125
126
    @classmethod
127
    def install(cls, args):
128
        """Install local or remote NApps."""
129
        cls.install_napps(args['<napp>'])
130
131
    @classmethod
132
    def install_napps(cls, napps):
133
        """Install local or remote NApps.
134
135
        This method is recursive, it will install each napps and your
136
        dependencies.
137
        """
138
        mgr = NAppsManager()
139
        for napp in napps:
140
            mgr.set_napp(*napp)
141
            LOG.info('  NApp %s:', mgr.napp_id)
142
143
            try:
144
                if not mgr.is_installed():
145
                    # Try to install all NApps, even if
146
                    # some of them fail.
147
                    cls.install_napp(mgr)
148
149
                    # Enable the NApp
150
                    if not mgr.is_enabled():
151
                        cls.enable_napp(mgr)
152
                        napp_dependencies = mgr.dependencies()
153
                        if napp_dependencies:
154
                            LOG.info('Installing Dependencies:')
155
                            cls.install_napps(napp_dependencies)
156
                    else:
157
                        LOG.info('    Enabled.')
158
                else:
159
                    LOG.warning('  Napp already installed.')
160
            except KytosException:
161
                LOG.error('Error installing NApp.')
162
                continue
163
164
    @classmethod
165
    def install_napp(cls, mgr):
166
        """Install a NApp.
167
168
        Raises:
169
            KytosException: If a NApp hasn't been found.
170
171
        """
172
        LOG.info('    Downloading from NApps Server...')
173
        try:
174
            mgr.remote_install()
175
            LOG.info('    Downloaded and installed.')
176
            return
177
        except HTTPError as exception:
178
            if exception.code == 404:
179
                LOG.error('    NApp not found.')
180
            else:
181
                LOG.error('    NApps Server error: %s', exception)
182
        except URLError as exception:
183
            LOG.error('    NApps Server error: %s', str(exception.reason))
184
        raise KytosException("NApp not found.")
185
186
    @classmethod
187
    def search(cls, args):
188
        """Search for NApps in NApps server matching a pattern."""
189
        safe_shell_pat = re.escape(args['<pattern>']).replace(r'\*', '.*')
190
        pat_str = '.*{}.*'.format(safe_shell_pat)
191
        pattern = re.compile(pat_str, re.IGNORECASE)
192
        remote_json = NAppsManager.search(pattern)
193
        remote = set()
194
        for napp in remote_json:
195
            # WARNING: This will be changed in future versions, when 'author'
196
            # will be removed.
197
            username = napp.get('username', napp.get('author'))
198
            remote.add(((username, napp.get('name')), napp.get('description')))
199
200
        cls._print_napps(remote)
201
202
    @classmethod
203
    def _print_napps(cls, napp_list):
204
        """Format the NApp list to be printed."""
205
        mgr = NAppsManager()
206
        enabled = mgr.get_enabled()
207
        installed = mgr.get_installed()
208
        napps = []
209
        for napp, desc in sorted(napp_list):
210
            status = 'i' if napp in installed else '-'
211
            status += 'e' if napp in enabled else '-'
212
            status = '[{}]'.format(status)
213
            name = '{}/{}'.format(*napp)
214
            napps.append((status, name, desc))
215
        cls.print_napps(napps)
216
217
    @classmethod
218
    def list(cls, args):  # pylint: disable=unused-argument
219
        """List all installed NApps and inform whether they are enabled."""
220
        mgr = NAppsManager()
221
222
        # Add status
223
        napps = [napp + ('[ie]',) for napp in mgr.get_enabled()]
224
        napps += [napp + ('[i-]',) for napp in mgr.get_disabled()]
225
226
        # Sort, add description and reorder columns
227
        napps.sort()
228
        napps_ordered = []
229
        for user, name, status in napps:
230
            description = mgr.get_description(user, name)
231
            version = mgr.get_version(user, name)
232
            napp_id = f'{user}/{name}'
233
            if version:
234
                napp_id += f':{version}'
235
236
            napps_ordered.append((status, napp_id, description))
237
238
        cls.print_napps(napps_ordered)
239
240
    @staticmethod
241
    def print_napps(napps):
242
        """Print status, name and description."""
243
        if not napps:
244
            print('No NApps found.')
245
            return
246
247
        stat_w = 6  # We already know the size of Status col
248
        name_w = max(len(n[1]) for n in napps)
249
        desc_w = max(len(n[2]) for n in napps)
250
        term_w = os.popen('stty size', 'r').read().split()[1]
251
        remaining = max(0, int(term_w) - stat_w - name_w - 6)
252
        desc_w = min(desc_w, remaining)
253
        widths = (stat_w, name_w, desc_w)
254
255
        header = '\n{:^%d} | {:^%d} | {:^%d}' % widths
256
        row = '{:^%d} | {:<%d} | {:<%d}' % widths
257
        print(header.format('Status', 'NApp ID', 'Description'))
258
        print('=+='.join('=' * w for w in widths))
259
        for user, name, desc in napps:
260
            desc = (desc[:desc_w - 3] + '...') if len(desc) > desc_w else desc
261
            print(row.format(user, name, desc))
262
263
        print('\nStatus: (i)nstalled, (e)nabled\n')
264
265
    @staticmethod
266
    def delete(args):
267
        """Delete NApps from server."""
268
        mgr = NAppsManager()
269
        for napp in args['<napp>']:
270
            mgr.set_napp(*napp)
271
            LOG.info('Deleting NApp %s from server...', mgr.napp_id)
272
            try:
273
                mgr.delete()
274
                LOG.info('  Deleted.')
275
            except requests.HTTPError as exception:
276
                if exception.response.status_code == 405:
277
                    LOG.error('Delete Napp is not allowed yet.')
278
                else:
279
                    msg = json.loads(exception.response.content)
280
                    LOG.error('  Server error: %s - ', msg['error'])
281
282
    @classmethod
283
    def prepare(cls, args):  # pylint: disable=unused-argument
284
        """Create OpenAPI v3.0 spec skeleton."""
285
        mgr = NAppsManager()
286
        mgr.prepare()
287
288
    @classmethod
289
    def reload(cls, args):
290
        """Reload NApps code."""
291
        LOG.info('Reloading NApps...')
292
        mgr = NAppsManager()
293
294
        try:
295
            if args['all']:
296
                mgr.reload(None)
297
            else:
298
                napps = args['<napp>']
299
                mgr.reload(napps)
300
301
            LOG.info('\tReloaded.')
302
        except requests.HTTPError as exception:
303
            if exception.response.status_code != 200:
304
                msg = json.loads(exception.response.content)
305
                LOG.error('\tServer error: %s - ', msg['error'])
306