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

kytos.cli.commands.napps.api   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 306
Duplicated Lines 0 %

Test Coverage

Coverage 84.78%

Importance

Changes 0
Metric Value
eloc 204
dl 0
loc 306
rs 6.96
c 0
b 0
f 0
ccs 156
cts 184
cp 0.8478
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.enable() 0 11 2
A NAppsAPI.install_napp() 0 21 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
A NAppsAPI._print_napps() 0 14 4
A NAppsAPI.search() 0 15 2
A NAppsAPI.list() 0 22 3
A NAppsAPI.print_napps() 0 24 4
A NAppsAPI.reload() 0 18 4

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 1
        safe_shell_pat = re.escape(args['<pattern>']).replace(r'\*', '.*')
190 1
        pat_str = '.*{}.*'.format(safe_shell_pat)
191 1
        pattern = re.compile(pat_str, re.IGNORECASE)
192 1
        remote_json = NAppsManager.search(pattern)
193 1
        remote = set()
194 1
        for napp in remote_json:
195
            # WARNING: This will be changed in future versions, when 'author'
196
            # will be removed.
197 1
            username = napp.get('username', napp.get('author'))
198 1
            remote.add(((username, napp.get('name')), napp.get('description')))
199
200 1
        cls._print_napps(remote)
201
202 1
    @classmethod
203
    def _print_napps(cls, napp_list):
204
        """Format the NApp list to be printed."""
205 1
        mgr = NAppsManager()
206 1
        enabled = mgr.get_enabled()
207 1
        installed = mgr.get_installed()
208 1
        napps = []
209 1
        for napp, desc in sorted(napp_list):
210 1
            status = 'i' if napp in installed else '-'
211 1
            status += 'e' if napp in enabled else '-'
212 1
            status = '[{}]'.format(status)
213 1
            name = '{}/{}'.format(*napp)
214 1
            napps.append((status, name, desc))
215 1
        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 1
        mgr = NAppsManager()
221
222
        # Add status
223 1
        napps = [napp + ('[ie]',) for napp in mgr.get_enabled()]
224 1
        napps += [napp + ('[i-]',) for napp in mgr.get_disabled()]
225
226
        # Sort, add description and reorder columns
227 1
        napps.sort()
228 1
        napps_ordered = []
229 1
        for user, name, status in napps:
230 1
            description = mgr.get_description(user, name)
231 1
            version = mgr.get_version(user, name)
232 1
            napp_id = f'{user}/{name}'
233 1
            if version:
234 1
                napp_id += f':{version}'
235
236 1
            napps_ordered.append((status, napp_id, description))
237
238 1
        cls.print_napps(napps_ordered)
239
240 1
    @staticmethod
241
    def print_napps(napps):
242
        """Print status, name and description."""
243 1
        if not napps:
244
            print('No NApps found.')
245
            return
246
247 1
        stat_w = 6  # We already know the size of Status col
248 1
        name_w = max(len(n[1]) for n in napps)
249 1
        desc_w = max(len(n[2]) for n in napps)
250 1
        term_w = os.popen('stty size', 'r').read().split()[1]
251 1
        remaining = max(0, int(term_w) - stat_w - name_w - 6)
252 1
        desc_w = min(desc_w, remaining)
253 1
        widths = (stat_w, name_w, desc_w)
254
255 1
        header = '\n{:^%d} | {:^%d} | {:^%d}' % widths
256 1
        row = '{:^%d} | {:<%d} | {:<%d}' % widths
257 1
        print(header.format('Status', 'NApp ID', 'Description'))
258 1
        print('=+='.join('=' * w for w in widths))
259 1
        for user, name, desc in napps:
260 1
            desc = (desc[:desc_w - 3] + '...') if len(desc) > desc_w else desc
261 1
            print(row.format(user, name, desc))
262
263 1
        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 1
        except requests.HTTPError as exception:
303 1
            if exception.response.status_code != 200:
304 1
                msg = json.loads(exception.response.content)
305
                LOG.error('\tServer error: %s - ', msg['error'])
306