Test Failed
Pull Request — master (#277)
by Gleyberson
01:32
created

kytos.cli.commands.napps.api   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 306
Duplicated Lines 0 %

Test Coverage

Coverage 55.98%

Importance

Changes 0
Metric Value
eloc 204
dl 0
loc 306
ccs 103
cts 184
cp 0.5598
rs 6.96
c 0
b 0
f 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 1
import json
3 1
import logging
4 1
import os
5 1
import re
6 1
from urllib.error import HTTPError, URLError
7
8 1
import requests
9
10 1
from kytos.utils.exceptions import KytosException
11 1
from kytos.utils.napps import NAppsManager
12
13 1
LOG = logging.getLogger(__name__)
14
15
16 1
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 1
    @classmethod
25
    def disable(cls, args):
26
        """Disable subcommand."""
27 1
        mgr = NAppsManager()
28
29 1
        if args['all']:
30 1
            napps = mgr.get_enabled()
31
        else:
32 1
            napps = args['<napp>']
33
34 1
        for napp in napps:
35 1
            mgr.set_napp(*napp)
36 1
            LOG.info('NApp %s:', mgr.napp_id)
37 1
            cls.disable_napp(mgr)
38
39 1
    @staticmethod
40
    def disable_napp(mgr):
41
        """Disable a NApp."""
42 1
        if mgr.is_enabled():
43 1
            LOG.info('  Disabling...')
44 1
            mgr.disable()
45 1
            LOG.info('  Disabled.')
46
        else:
47 1
            LOG.error("  NApp isn't enabled.")
48
49 1
    @classmethod
50
    def enable(cls, args):
51
        """Enable subcommand."""
52 1
        mgr = NAppsManager()
53
54 1
        if args['all']:
55 1
            napps = mgr.get_disabled()
56
        else:
57 1
            napps = args['<napp>']
58
59 1
        cls.enable_napps(napps)
60
61 1
    @classmethod
62
    def enable_napp(cls, mgr):
63
        """Install one NApp using NAppManager object."""
64 1
        try:
65 1
            if not mgr.is_enabled():
66 1
                LOG.info('    Enabling...')
67 1
                mgr.enable()
68
69
            # Check if NApp is enabled
70 1
            if mgr.is_enabled():
71 1
                LOG.info('    Enabled.')
72
            else:
73 1
                LOG.error('    Error enabling NApp.')
74
        except (FileNotFoundError, PermissionError) as exception:
75
            LOG.error('  %s', exception)
76
77 1
    @classmethod
78
    def enable_napps(cls, napps):
79
        """Enable a list of NApps.
80
81
        Args:
82
            napps (list): List of NApps.
83
        """
84 1
        mgr = NAppsManager()
85 1
        for napp in napps:
86 1
            mgr.set_napp(*napp)
87 1
            LOG.info('NApp %s:', mgr.napp_id)
88 1
            cls.enable_napp(mgr)
89
90 1
    @classmethod
91
    def create(cls, args):  # pylint: disable=unused-argument
92
        """Bootstrap a basic NApp structure on the current folder."""
93 1
        NAppsManager.create_napp(meta_package=args.get('--meta', False))
94
95 1
    @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 1
        try:
102 1
            NAppsManager().upload()
103
        except FileNotFoundError as err:
104
            LOG.error("Couldn't find %s in current directory.", err.filename)
105
106 1
    @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 1
        mgr = NAppsManager()
114 1
        for napp in args['<napp>']:
115 1
            mgr.set_napp(*napp)
116 1
            LOG.info('NApp %s:', mgr.napp_id)
117 1
            if mgr.is_installed():
118 1
                if mgr.is_enabled():
119 1
                    cls.disable_napp(mgr)
120 1
                LOG.info('  Uninstalling...')
121 1
                mgr.remote_uninstall()
122 1
                LOG.info('  Uninstalled.')
123
            else:
124
                LOG.error("  NApp isn't installed.")
125
126 1
    @classmethod
127
    def install(cls, args):
128
        """Install local or remote NApps."""
129 1
        cls.install_napps(args['<napp>'])
130
131 1
    @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 1
        mgr = NAppsManager()
139 1
        for napp in napps:
140 1
            mgr.set_napp(*napp)
141 1
            LOG.info('  NApp %s:', mgr.napp_id)
142
143 1
            try:
144 1
                if not mgr.is_installed():
145
                    # Try to install all NApps, even if
146
                    # some of them fail.
147 1
                    cls.install_napp(mgr)
148
149
                    # Enable the NApp
150 1
                    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 1
                        LOG.info('    Enabled.')
158
                else:
159
                    LOG.warning('  Napp already installed.')
160
            except KytosException:
161
                LOG.error('Error installing NApp.')
162
                continue
163
164 1
    @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 1
        LOG.info('    Downloading from NApps Server...')
173 1
        try:
174 1
            mgr.remote_install()
175 1
            LOG.info('    Downloaded and installed.')
176 1
            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 1
    @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 1
    @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 1
    @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 1
    @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 1
    @staticmethod
266
    def delete(args):
267
        """Delete NApps from server."""
268 1
        mgr = NAppsManager()
269 1
        for napp in args['<napp>']:
270 1
            mgr.set_napp(*napp)
271 1
            LOG.info('Deleting NApp %s from server...', mgr.napp_id)
272 1
            try:
273 1
                mgr.delete()
274 1
                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 1
    @classmethod
283
    def prepare(cls, args):  # pylint: disable=unused-argument
284
        """Create OpenAPI v3.0 spec skeleton."""
285 1
        mgr = NAppsManager()
286 1
        mgr.prepare()
287
288 1
    @classmethod
289
    def reload(cls, args):
290
        """Reload NApps code."""
291 1
        LOG.info('Reloading NApps...')
292 1
        mgr = NAppsManager()
293
294 1
        try:
295 1
            if args['all']:
296 1
                mgr.reload(None)
297
            else:
298 1
                napps = args['<napp>']
299 1
                mgr.reload(napps)
300
301 1
            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