kytos-utils-2019.2/0000755000175100001630000000000013577164176014737 5ustar runnerdocker00000000000000kytos-utils-2019.2/LICENSE0000644000175100001630000000206513577164155015744 0ustar runnerdocker00000000000000The MIT License (MIT) Copyright (c) 2016 Kytos Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kytos-utils-2019.2/bin/0000755000175100001630000000000013577164176015507 5ustar runnerdocker00000000000000kytos-utils-2019.2/bin/kytos0000755000175100001630000000334713577164155016612 0ustar runnerdocker00000000000000#!/usr/bin/env python3 # This file is part of kytos-utils. # # Copyright (c) 2016-2019 by Kytos Team. # # Authors: # Beraldo Leal """kytos - The kytos command line. Usage: kytos [-c |--config ] [...] kytos [-v|--version] kytos [-h|--help] Options: -c , --config Load config file [default: ~/.kytosrc] -h, --help Show this screen. -v, --version Show version. The most commonly used kytos commands are: napps Create, list, enable, install (and other actions) NApps. server Start, Stop your Kytos Controller (Kytos) web Manage the Web User Interface users Commands to handle users from NApps server. See 'kytos -h|--help' for more information on a specific command. """ import logging import os from docopt import docopt from kytos.utils.config import KytosConfig logging.basicConfig(format='%(levelname)-5s %(message)s', level=logging.INFO) if __name__ == '__main__': metadata = KytosConfig().get_metadata() version = metadata.get('__version__') args = docopt(__doc__, version='kytos command line, version %s' % version, options_first=True) command = args[''] command_args = args[''] argv = [command] + command_args if command == 'napps': from kytos.cli.commands.napps.parser import parse parse(argv) elif command == 'users': from kytos.cli.commands.users.parser import parse parse(argv) elif command == 'web': from kytos.cli.commands.web.parser import parse parse(argv) else: print("Error: Invalid syntax") exit(__doc__) kytos-utils-2019.2/AUTHORS.rst0000644000175100001630000000130213577164155016607 0ustar runnerdocker00000000000000####### Authors ####### - Beraldo Leal - Carlos Eduardo Moreira dos Santos - Diego Rabatone Oliveira - Macártur de Sousa Carvalho - Gleyberson Andrade - Humberto Diógenes - Lucas Felix - Rogerio Motitsuki Contributors ------------ - Luan Gonçalves - Erick Vermot - Vinicius Arcanjo - Carlos Magno Barbosa kytos-utils-2019.2/setup.py0000644000175100001630000001430113577164155016445 0ustar runnerdocker00000000000000"""Setup script. Run "python3 setup --help-commands" to list all available commands and their descriptions. """ import os import re import shutil import sys from abc import abstractmethod # Disabling checks due to https://github.com/PyCQA/pylint/issues/73 from distutils.command.clean import clean # pylint: disable=E0401,E0611 from subprocess import CalledProcessError, call, check_call from setuptools import Command, find_packages, setup from setuptools.command.develop import develop from setuptools.command.install import install if 'VIRTUAL_ENV' in os.environ: BASE_ENV = os.environ['VIRTUAL_ENV'] else: BASE_ENV = '/' ETC_KYTOS = 'etc/kytos' KYTOS_SKEL_PATH = 'etc/kytos/skel' USERNAME_PATH = os.path.join(KYTOS_SKEL_PATH, 'napp-structure/username') NAPP_PATH = os.path.join(USERNAME_PATH, 'napp') ETC_FILES = [(os.path.join(BASE_ENV, USERNAME_PATH), [os.path.join(USERNAME_PATH, '__init__.py')]), (os.path.join(BASE_ENV, NAPP_PATH), [os.path.join(NAPP_PATH, '__init__.py.template'), os.path.join(NAPP_PATH, 'kytos.json.template'), os.path.join(NAPP_PATH, 'openapi.yml.template'), os.path.join(NAPP_PATH, 'main.py.template'), os.path.join(NAPP_PATH, 'README.rst.template'), os.path.join(NAPP_PATH, 'settings.py.template')])] class SimpleCommand(Command): """Make Command implementation simpler.""" user_options = [] def __init__(self, *args, **kwargs): """Store arguments so it's possible to call other commands later.""" super().__init__(*args, **kwargs) self._args = args self._kwargs = kwargs @abstractmethod def run(self): """Run when command is invoked. Use *call* instead of *check_call* to ignore failures. """ def initialize_options(self): """Set default values for options.""" def finalize_options(self): """Post-process options.""" class Cleaner(clean): """Custom clean command to tidy up the project root.""" description = 'clean build, dist, pyc and egg from package and docs' def run(self): """Clean build, dist, pyc and egg from package and docs.""" super().run() call('rm -vrf ./build ./dist ./*.egg-info', shell=True) call('find . -name __pycache__ -type d | xargs rm -rf', shell=True) call('test -d docs && make -C docs/ clean', shell=True) class TestCoverage(SimpleCommand): """Display test coverage.""" description = 'run unit tests and display code coverage' def run(self): """Run unittest quietly and display coverage report.""" cmd = 'coverage3 run --source=kytos setup.py test && coverage3 report' check_call(cmd, shell=True) class CITest(SimpleCommand): """Run all CI tests.""" description = 'run all CI tests: unit and doc tests, linter' def run(self): """Run unit tests with coverage, doc tests and linter.""" for command in TestCoverage, Linter: command(*self._args, **self._kwargs).run() class Linter(SimpleCommand): """Code linters.""" description = 'lint Python source code' def run(self): """Run yala.""" print('Yala is running. It may take several seconds...') try: check_call('yala setup.py tests kytos', shell=True) print('No linter error found.') except CalledProcessError: print('Linter check failed. Fix the error(s) above and try again.') sys.exit(-1) class CommonInstall: """Class with common procedures to install the package.""" @staticmethod def _create_data_files_directory(symlink=False): """Install data_files in the /etc directory.""" current_directory = os.path.abspath(os.path.dirname(__file__)) etc_kytos = os.path.join(BASE_ENV, ETC_KYTOS) if not os.path.exists(etc_kytos): os.makedirs(etc_kytos) src = os.path.join(current_directory, KYTOS_SKEL_PATH) dst = os.path.join(BASE_ENV, KYTOS_SKEL_PATH) if os.path.exists(dst): if not os.listdir(dst): # Path already exists but it's empty, so we'll populate it # We remove it first to avoid an exception from copytree os.rmdir(dst) shutil.copytree(src, dst) else: # It doesn't exist yet, so we should symlink or copy contents if symlink: os.symlink(src, dst) else: shutil.copytree(src, dst) class InstallMode(install, CommonInstall): """Procedures to install the package.""" def run(self): """Install the package in a developer mode.""" super().run() self._create_data_files_directory() class DevelopMode(develop, CommonInstall): """Recommended setup for kytos-utils developers. Instead of copying the files to the expected directories, a symlink is created on the system aiming the current source code. """ def run(self): """Install the package in a developer mode.""" super().run() self._create_data_files_directory(True) # We are parsing the metadata file as if it was a text file because if we # import it as a python module, necessarily the kytos.utils module would be # initialized. META_FILE = open("kytos/utils/metadata.py").read() METADATA = dict(re.findall(r"(__[a-z]+__)\s*=\s*'([^']+)'", META_FILE)) setup(name='kytos-utils', version=METADATA.get('__version__'), description=METADATA.get('__description__'), url=METADATA.get('__url__'), author=METADATA.get('__author__'), author_email=METADATA.get('__author_email__'), license=METADATA.get('__license__'), test_suite='tests', include_package_data=True, scripts=['bin/kytos'], install_requires=[line.strip() for line in open("requirements/run.txt").readlines() if not line.startswith('#')], packages=find_packages(exclude=['tests']), cmdclass={ 'ci': CITest, 'clean': Cleaner, 'coverage': TestCoverage, 'develop': DevelopMode, 'install': InstallMode, 'lint': Linter }, zip_safe=False) kytos-utils-2019.2/CHANGELOG.rst0000644000175100001630000002122713577164155016761 0ustar runnerdocker00000000000000######### Changelog ######### This is a log of changes made to the *kytos-utils* project. UNRELEASED - Under development ****************************** Added ===== Changed ======= Deprecated ========== Removed ======= Fixed ===== Security ======== [2019.2] - "gil" stable - 2019-12-20 ************************************ This is the stable version based on the last beta pre-releases. Fixed ===== - Fixed a problem on the `.gitignore` parser when packaging a NApp. [2019.2rc1] - "gil" release candidate 1 - 2019-12-13 **************************************************** No major changes since the last pre-release. [2019.2b3] - "gil" beta3 - 2019-12-06 ************************************* Added ===== - Added a warning to alert users when there's a version mismatch between kytos-utils and kytos core. [2019.2b2] - "gil" beta2 - 2019-10-18 ************************************** No changes since last pre-release. [2019.2b1] - "gil" beta1 - 2019-08-30 ************************************* Changed ======= - Improved installation of dependencies - pinned versions for dependencies in the production and developer install modes. Removed ======= - Removed local NApp installation - now this is done by Kytos core. [2019.1] - "fafa" stable - 2019-07-12 ************************************* - This is the stable version based on the last beta pre-releases. No changes since the last rc1. [2019.1rc1] - "fafa" rc1 - 2019-07-05 ************************************** Changed ======= - A better log message when bad requests are sent over REST API Fixed ===== - Fixed NApp package structure [2019.1b3] - "fafa" beta3 - 2019-06-17 ************************************** Added ===== - kytos-utils now can be installed on a remote machine - New unit tests in order to cover Napps.Manager - Coverage configuration file Changed ======= - When packaging a NApp, kytos-utils will ignore files listed on .gitignore, creating smaller NApps - Improved Scrutinizer configuration - Better error message when connecting to kytosd Removed ======= - Removed kytos-core dependency in order to allow standalone installation Fixed ===== - Few Linter issues Security ======== - Updated requirements versions in order to fix some security bugs [2019.1b2] - "fafa" beta2 - 2019-05-03 ************************************** Fixed ===== - Fixed packaging before uploading NApps. - Fixed initial version number when creating a new NApp. [2019.1b1] - "fafa" beta1 - 2019-03-15 ************************************** Added ===== - Added a global and explicit SKEL_PATH constant to get skel from the new location. Changed ======= - mkdir call replaced by makedirs in order to make installation more reliable. - Updated requirements versions to match Kytos core. Deprecated ========== Removed ======= Fixed ===== - Fixed some linter issues. - Populate /etc/kytos/skel even if exists and it is empty. Security ======== [2018.2] - "ernesto" stable - 2018-12-30 **************************************** - This is the stable version based on the last beta pre-releases. No changes since the last rc1. [2018.2rc1] - "ernesto" rc - 2018-12-21 ***************************************** Added ===== - Support for meta-napps (beta) [2018.2b3] - "ernesto" beta3 - 2018-12-14 ***************************************** Fixed ===== - Enhanced error handling when installing invalid NApps - Fixed Kytos skel location to be compliant with Debian policy [2018.2b2] - "ernesto" beta2 - 2018-10-15 ***************************************** Added ===== - Added flag --meta to create a new NApp with meta-package structure. Fixed ===== - Fixed bug when creating NApp (#190) - Fixed some linter erros [2018.2b1] - "ernesto" beta1 - 2018-09-06 ***************************************** Nothing has changed since 2018.1rc1 [2018.1rc1] - "dalva" release candidate - 2018-06-29 **************************************************** Fixed ===== - Fixed small bug [2018.1b3] - "dalva" beta3 - 2018-06-15 *************************************** Added ===== - `kytos napps reload /` will reload the NApp code - `kytos napps reload all` command to update the NApp code of all NApps Changed ======= - Improved log error messages [2018.1b2] - "dalva" beta2 - 2018-04-20 ************************************** Added ===== - `kytos napps create` will create the ui folder [`ui/k-toolbar`, `ui/k-menu-bar`, `k-info-panel`] when creating a new Napp structure - `kytos web update ` command to update the Kytos Web User Interface with a specific version Fixed ===== - Fix some docstring and comments [2018.1b1] - "dalva" beta1 - 2018-03-09 ************************************** Nothing has changed since 2017.2 [2017.2] - "chico" stable - 2017-12-21 ************************************** Nothing has changed since 2017.2rc1 [2017.2rc1] - "chico" release candidate 1 - 2017-12-15 ****************************************************** Added ===== - `kytos web update` command to update the Kytos Web User Interface to the latest version. [2017.2b2] - "chico" beta2 - 2017-12-01 *************************************** Added ===== - `kytos napps prepare` command to generate openapi.yml skeleton file Changed ======= - Dependency installation/update for devs: `pip install -Ur requirements/dev.txt`. To use cloned kytos repos as dependencies, reinstall that repos with `pip install -e .` in the end. - Improvements on napps dependencies management. Fixed ===== - Linter issues. - Unneeded running Kytosd requirement. [2017.2b1] - "chico" beta1 - 2017-09-19 *************************************** Added ===== - Version tags - now NApps fully support the /: format. - Create an OpenAPI skeleton based on NApp's rest decorators. Changed ======= - NApps will now install other NApps listed as dependencies. - Do not require a running kytosd for some commands. - Yala substitutes Pylama as the main linter checker. - Requirements files updated and restructured. Fixed ===== - Some test features. - Some bug fixes. [2017.1] - 'bethania' - 2017-07-06 ********************************** Fixed ===== - NApp skel to match changes in Kytos [2017.1b3] - "bethania" beta3 - 2017-06-16 ****************************************** Added ===== - Commands to enable/disable all installed NApps (`kytos napps all`). Changed ======= - Install and enable NApps based on Kytos instance. `kytos-utils` will request the configuration loaded by kytos before managing NApps. Removed ======= - Support for NApp management whithout a Kytos running instance. Fixed ===== - A few bug fixes. [2017.1b2] - "bethania" beta2 - 2017-05-05 ****************************************** Added ===== - :code:`kytos users register` command can be used to register a new user in the NApps server. - Now under MIT license. Changed ======= - skel templates updated to match changes in logging and kytos.json. - Improved tests and style check for developers, and added continuous integration. Deprecated ========== - kytos.json 'author' attribute is being replaced by 'username' due to context, and is deprecated. It will be removed in future releases. Removed ======= - kytos.json 'long_description' attribute is no longer necessary nor available. The detailed description shall now be in README.rst. Fixed ===== - Now creates the NApps directory structure when it does not exist. - Pypi package is fixed and working. - Several bug fixes. [2017.1b1] - "bethania" beta1 - 2017-03-24 ****************************************** Added ===== - etc/skel files, with templates to create all the necessary NApp files when executing :code:`kytos napps create`. - Command line tool to manage the kytos NApps. A set of commands to help managing NApps. - May now use the command line to: - Create new NApps. - Install NApps created locally or from the NApps server. - Enable/disable installed NApps. - List installed / enabled NApps. - Search for NApps in the NApps server. - Upload NApps to the server. - Help is available for command line tools. Appending :code:`--help` to the end of a command displays useful information about it. Changed ======= - Setup script now installs all the requirements during the setup process. There is no need to worry about them beforehand. - Updated to Python 3.6. - Several bug fixes. - Separate CLI code from NApps code: refactored code to make clear what is related to the command line tools and what is related to the kytos NApps. - Clean and descriptive log messages. Security ======== - Authentication for NApps upload process - there is need for an account in the `NApps server `__ to upload any NApp. kytos-utils-2019.2/kytos/0000755000175100001630000000000013577164176016110 5ustar runnerdocker00000000000000kytos-utils-2019.2/kytos/__init__.py0000644000175100001630000000014613577164155020217 0ustar runnerdocker00000000000000"""Kytos SDN Platform.""" from pkgutil import extend_path __path__ = extend_path(__path__, __name__) kytos-utils-2019.2/kytos/cli/0000755000175100001630000000000013577164176016657 5ustar runnerdocker00000000000000kytos-utils-2019.2/kytos/cli/__init__.py0000644000175100001630000000002413577164155020761 0ustar runnerdocker00000000000000"""CLI commands.""" kytos-utils-2019.2/kytos/cli/commands/0000755000175100001630000000000013577164176020460 5ustar runnerdocker00000000000000kytos-utils-2019.2/kytos/cli/commands/__init__.py0000644000175100001630000000003213577164155022561 0ustar runnerdocker00000000000000"""Basic CLI commands.""" kytos-utils-2019.2/kytos/cli/commands/web/0000755000175100001630000000000013577164176021235 5ustar runnerdocker00000000000000kytos-utils-2019.2/kytos/cli/commands/web/parser.py0000644000175100001630000000155713577164155023110 0ustar runnerdocker00000000000000"""kytos - The kytos command line. You are at the "web" command. Usage: kytos web update kytos web update Options: -h, --help Show this screen. Common web subcommands: update Update the web-ui with the latest version """ import sys from docopt import docopt from kytos.cli.commands.web.api import WebAPI from kytos.utils.config import KytosConfig from kytos.utils.exceptions import KytosException def parse(argv): """Parse cli args.""" args = docopt(__doc__, argv=argv) try: call(sys.argv[2], args) except KytosException as exception: print("Error parsing args: {}".format(exception)) exit() def call(subcommand, args): # pylint: disable=unused-argument """Call a subcommand passing the args.""" KytosConfig.check_versions() func = getattr(WebAPI, subcommand) func(args) kytos-utils-2019.2/kytos/cli/commands/web/__init__.py0000644000175100001630000000003013577164155023334 0ustar runnerdocker00000000000000"""WEB CLI Commands.""" kytos-utils-2019.2/kytos/cli/commands/web/api.py0000644000175100001630000000173213577164155022360 0ustar runnerdocker00000000000000"""Translate cli commands to non-cli code.""" import logging from urllib.error import HTTPError, URLError import requests from kytos.utils.config import KytosConfig LOG = logging.getLogger(__name__) class WebAPI: # pylint: disable=too-few-public-methods """An API for the command-line interface.""" @classmethod def update(cls, args): """Call the method to update the Web UI.""" kytos_api = KytosConfig().config.get('kytos', 'api') url = f"{kytos_api}api/kytos/core/web/update" version = args[""] if version: url += f"/{version}" try: result = requests.post(url) except(HTTPError, URLError, requests.exceptions.ConnectionError): LOG.error("Can't connect to server: %s", kytos_api) return if result.status_code != 200: LOG.info("Error while updating web ui: %s", result.content) else: LOG.info("Web UI updated.") kytos-utils-2019.2/kytos/cli/commands/users/0000755000175100001630000000000013577164176021621 5ustar runnerdocker00000000000000kytos-utils-2019.2/kytos/cli/commands/users/parser.py0000644000175100001630000000154313577164155023467 0ustar runnerdocker00000000000000"""kytos - The kytos command line. You are at the "users" command. Usage: kytos users register kytos users -h | --help Options: -h, --help Show this screen. Common user subcommands: register Register a new user to upload napps to Napps Server. """ import sys from docopt import docopt from kytos.cli.commands.users.api import UsersAPI from kytos.utils.config import KytosConfig from kytos.utils.exceptions import KytosException def parse(argv): """Parse cli args.""" args = docopt(__doc__, argv=argv) try: call(sys.argv[2], args) except KytosException as exception: print("Error parsing args: {}".format(exception)) exit() def call(subcommand, args): """Call a subcommand passing the args.""" KytosConfig.check_versions() func = getattr(UsersAPI, subcommand) func(args) kytos-utils-2019.2/kytos/cli/commands/users/__init__.py0000644000175100001630000000003213577164155023722 0ustar runnerdocker00000000000000"""USERS CLI Commands.""" kytos-utils-2019.2/kytos/cli/commands/users/api.py0000644000175100001630000000117213577164155022742 0ustar runnerdocker00000000000000"""Translate cli commands to non-cli code.""" import logging from kytos.utils.users import UsersManager LOG = logging.getLogger(__name__) class UsersAPI: """An API for the command-line interface. Use the config file only for required options. Static methods are called by the parser and they instantiate an object of this class to fulfill the request. """ user_manager = UsersManager() @classmethod def register(cls, args): # pylint: disable=unused-argument """Create a new user and register it on the Napps server.""" result = cls.user_manager.register() print(result) kytos-utils-2019.2/kytos/cli/commands/napps/0000755000175100001630000000000013577164176021601 5ustar runnerdocker00000000000000kytos-utils-2019.2/kytos/cli/commands/napps/parser.py0000644000175100001630000000701313577164155023445 0ustar runnerdocker00000000000000"""kytos - The kytos command line. You are at the "napps" command. Usage: kytos napps create [--meta] kytos napps prepare kytos napps upload kytos napps delete ... kytos napps list kytos napps install ... kytos napps uninstall ... kytos napps enable (all| ...) kytos napps disable (all| ...) kytos napps reload (all| ...) kytos napps search kytos napps -h | --help Options: -h, --help Show this screen. Common napps subcommands: create Create a bootstrap NApp structure for development. prepare Prepare NApp to be uploaded (called by "upload"). upload Upload current NApp to Kytos repository. delete Delete NApps from NApps Server. list List all NApps installed into your system. install Install a local or remote NApp into a controller. uninstall Remove a NApp from your controller. enable Enable a installed NApp. disable Disable a NApp. reload Reload NApps code. search Search for NApps in NApps Server. """ import re import sys from docopt import docopt from kytos.cli.commands.napps.api import NAppsAPI from kytos.utils.config import KytosConfig from kytos.utils.exceptions import KytosException def parse(argv): """Parse cli args.""" args = docopt(__doc__, argv=argv) try: call(sys.argv[2], args) except KytosException as exception: print("Error parsing args: {}".format(exception)) exit() def call(subcommand, args): """Call a subcommand passing the args.""" KytosConfig.check_versions() args[''] = parse_napps(args['']) func = getattr(NAppsAPI, subcommand) func(args) def parse_napps(napp_ids): """Return a list of tuples with username, napp_name and version. napp_ids elements are of the form username/name[:version] (version is optional). If no version is found, it will be None. If napp_ids is equal to 'all', this string will be returned. Args: napp_ids (list): NApps from the cli. Return: list: list of tuples with (username, napp_name, version). Raises: KytosException: If a NApp has not the form _username/name_. """ if 'all' in napp_ids: return 'all' return [parse_napp(napp_id) for napp_id in napp_ids] def parse_napp(napp_id): """Convert a napp_id in tuple with username, napp name and version. Args: napp_id: String with the form 'username/napp[:version]' (version is optional). If no version is found, it will be None. Returns: tuple: A tuple with (username, napp, version) Raises: KytosException: If a NApp has not the form _username/name_. """ # `napp_id` regex, composed by two mandatory parts (username, napp_name) # and one optional (version). # username and napp_name need to start with a letter, are composed of # letters, numbers and uderscores and must have at least three characters. # They are separated by a colon. # version is optional and can take any format. Is is separated by a hyphen, # if a version is defined. regex = r'([a-zA-Z][a-zA-Z0-9_]{2,})/([a-zA-Z][a-zA-Z0-9_]{2,}):?(.+)?' compiled_regex = re.compile(regex) matched = compiled_regex.fullmatch(napp_id) if not matched: msg = '"{}" NApp has not the form username/napp_name[:version].' raise KytosException(msg.format(napp_id)) return matched.groups() kytos-utils-2019.2/kytos/cli/commands/napps/__init__.py0000644000175100001630000000003213577164155023702 0ustar runnerdocker00000000000000"""NAPPS CLI Commands.""" kytos-utils-2019.2/kytos/cli/commands/napps/api.py0000644000175100001630000002333413577164155022726 0ustar runnerdocker00000000000000"""Translate cli commands to non-cli code.""" import json import logging import os import re from urllib.error import HTTPError, URLError import requests from kytos.utils.exceptions import KytosException from kytos.utils.napps import NAppsManager LOG = logging.getLogger(__name__) class NAppsAPI: """An API for the command-line interface. Use the config file only for required options. Static methods are called by the parser and they instantiate an object of this class to fulfill the request. """ @classmethod def disable(cls, args): """Disable subcommand.""" mgr = NAppsManager() if args['all']: napps = mgr.get_enabled() else: napps = args[''] for napp in napps: mgr.set_napp(*napp) LOG.info('NApp %s:', mgr.napp_id) cls.disable_napp(mgr) @staticmethod def disable_napp(mgr): """Disable a NApp.""" if mgr.is_enabled(): LOG.info(' Disabling...') mgr.disable() LOG.info(' Disabled.') else: LOG.error(" NApp isn't enabled.") @classmethod def enable(cls, args): """Enable subcommand.""" mgr = NAppsManager() if args['all']: napps = mgr.get_disabled() else: napps = args[''] cls.enable_napps(napps) @classmethod def enable_napp(cls, mgr): """Install one NApp using NAppManager object.""" try: if not mgr.is_enabled(): LOG.info(' Enabling...') mgr.enable() # Check if NApp is enabled if mgr.is_enabled(): LOG.info(' Enabled.') else: LOG.error(' Error enabling NApp.') except (FileNotFoundError, PermissionError) as exception: LOG.error(' %s', exception) @classmethod def enable_napps(cls, napps): """Enable a list of NApps. Args: napps (list): List of NApps. """ mgr = NAppsManager() for napp in napps: mgr.set_napp(*napp) LOG.info('NApp %s:', mgr.napp_id) cls.enable_napp(mgr) @classmethod def create(cls, args): # pylint: disable=unused-argument """Bootstrap a basic NApp structure on the current folder.""" NAppsManager.create_napp(meta_package=args.get('--meta', False)) @classmethod def upload(cls, args): # pylint: disable=unused-argument """Upload the NApp to the NApps server. Create the NApp package and upload it to the NApp server. """ try: NAppsManager().upload() except FileNotFoundError as err: LOG.error("Couldn't find %s in current directory.", err.filename) @classmethod def uninstall(cls, args): """Uninstall and delete NApps. For local installations, do not delete code outside install_path and enabled_path. """ mgr = NAppsManager() for napp in args['']: mgr.set_napp(*napp) LOG.info('NApp %s:', mgr.napp_id) if mgr.is_installed(): if mgr.is_enabled(): cls.disable_napp(mgr) LOG.info(' Uninstalling...') mgr.remote_uninstall() LOG.info(' Uninstalled.') else: LOG.error(" NApp isn't installed.") @classmethod def install(cls, args): """Install local or remote NApps.""" cls.install_napps(args['']) @classmethod def install_napps(cls, napps): """Install local or remote NApps. This method is recursive, it will install each napps and your dependencies. """ mgr = NAppsManager() for napp in napps: mgr.set_napp(*napp) LOG.info(' NApp %s:', mgr.napp_id) try: if not mgr.is_installed(): # Try to install all NApps, even if # some of them fail. cls.install_napp(mgr) # Enable the NApp if not mgr.is_enabled(): cls.enable_napp(mgr) napp_dependencies = mgr.dependencies() if napp_dependencies: LOG.info('Installing Dependencies:') cls.install_napps(napp_dependencies) else: LOG.info(' Enabled.') else: LOG.warning(' Napp already installed.') except KytosException: LOG.error('Error installing NApp.') continue @classmethod def install_napp(cls, mgr): """Install a NApp. Raises: KytosException: If a NApp hasn't been found. """ LOG.info(' Downloading from NApps Server...') try: mgr.remote_install() LOG.info(' Downloaded and installed.') return except HTTPError as exception: if exception.code == 404: LOG.error(' NApp not found.') else: LOG.error(' NApps Server error: %s', exception) except URLError as exception: LOG.error(' NApps Server error: %s', str(exception.reason)) raise KytosException("NApp not found.") @classmethod def search(cls, args): """Search for NApps in NApps server matching a pattern.""" safe_shell_pat = re.escape(args['']).replace(r'\*', '.*') pat_str = '.*{}.*'.format(safe_shell_pat) pattern = re.compile(pat_str, re.IGNORECASE) remote_json = NAppsManager.search(pattern) remote = set() for napp in remote_json: # WARNING: This will be changed in future versions, when 'author' # will be removed. username = napp.get('username', napp.get('author')) remote.add(((username, napp.get('name')), napp.get('description'))) cls._print_napps(remote) @classmethod def _print_napps(cls, napp_list): """Format the NApp list to be printed.""" mgr = NAppsManager() enabled = mgr.get_enabled() installed = mgr.get_installed() napps = [] for napp, desc in sorted(napp_list): status = 'i' if napp in installed else '-' status += 'e' if napp in enabled else '-' status = '[{}]'.format(status) name = '{}/{}'.format(*napp) napps.append((status, name, desc)) cls.print_napps(napps) @classmethod def list(cls, args): # pylint: disable=unused-argument """List all installed NApps and inform whether they are enabled.""" mgr = NAppsManager() # Add status napps = [napp + ('[ie]',) for napp in mgr.get_enabled()] napps += [napp + ('[i-]',) for napp in mgr.get_disabled()] # Sort, add description and reorder columns napps.sort() napps_ordered = [] for user, name, status in napps: description = mgr.get_description(user, name) version = mgr.get_version(user, name) napp_id = f'{user}/{name}' if version: napp_id += f':{version}' napps_ordered.append((status, napp_id, description)) cls.print_napps(napps_ordered) @staticmethod def print_napps(napps): """Print status, name and description.""" if not napps: print('No NApps found.') return stat_w = 6 # We already know the size of Status col name_w = max(len(n[1]) for n in napps) desc_w = max(len(n[2]) for n in napps) term_w = os.popen('stty size', 'r').read().split()[1] remaining = max(0, int(term_w) - stat_w - name_w - 6) desc_w = min(desc_w, remaining) widths = (stat_w, name_w, desc_w) header = '\n{:^%d} | {:^%d} | {:^%d}' % widths row = '{:^%d} | {:<%d} | {:<%d}' % widths print(header.format('Status', 'NApp ID', 'Description')) print('=+='.join('=' * w for w in widths)) for user, name, desc in napps: desc = (desc[:desc_w - 3] + '...') if len(desc) > desc_w else desc print(row.format(user, name, desc)) print('\nStatus: (i)nstalled, (e)nabled\n') @staticmethod def delete(args): """Delete NApps from server.""" mgr = NAppsManager() for napp in args['']: mgr.set_napp(*napp) LOG.info('Deleting NApp %s from server...', mgr.napp_id) try: mgr.delete() LOG.info(' Deleted.') except requests.HTTPError as exception: if exception.response.status_code == 405: LOG.error('Delete Napp is not allowed yet.') else: msg = json.loads(exception.response.content) LOG.error(' Server error: %s - ', msg['error']) @classmethod def prepare(cls, args): # pylint: disable=unused-argument """Create OpenAPI v3.0 spec skeleton.""" mgr = NAppsManager() mgr.prepare() @classmethod def reload(cls, args): """Reload NApps code.""" LOG.info('Reloading NApps...') mgr = NAppsManager() try: if args['all']: mgr.reload(None) else: napps = args[''] mgr.reload(napps) LOG.info('\tReloaded.') except requests.HTTPError as exception: if exception.response.status_code != 200: msg = json.loads(exception.response.content) LOG.error('\tServer error: %s - ', msg['error']) kytos-utils-2019.2/kytos/utils/0000755000175100001630000000000013577164176017250 5ustar runnerdocker00000000000000kytos-utils-2019.2/kytos/utils/exceptions.py0000644000175100001630000000014713577164155022002 0ustar runnerdocker00000000000000"""Kytos utils exceptions.""" class KytosException(Exception): """Kytos utils main exception.""" kytos-utils-2019.2/kytos/utils/__init__.py0000644000175100001630000000003713577164155021356 0ustar runnerdocker00000000000000"""Utility modules for CLI.""" kytos-utils-2019.2/kytos/utils/openapi.py0000644000175100001630000001340513577164155021255 0ustar runnerdocker00000000000000"""Deal with OpenAPI v3.""" import json import re from jinja2 import Environment, FileSystemLoader class OpenAPI: # pylint: disable=too-few-public-methods """Create OpenAPI skeleton.""" def __init__(self, napp_path, tpl_path): """Instantiate an OpenAPI object. Args: napp_path (string): Napp directory tlp_path (string): File name from template """ self._napp_path = napp_path self._template = tpl_path / 'openapi.yml.template' self._api_file = napp_path / 'openapi.yml' self._napp_dict = self._parse_napp_metadata() # Data for a path self._summary = None self._description = None # Part of template context self._paths = {} def render_template(self): """Render and save API doc in openapi.yml.""" self._parse_paths() context = dict(napp=self._napp_dict, paths=self._paths) self._save(context) def _parse_napp_metadata(self): """Return a NApp metadata file.""" filename = self._napp_path / 'kytos.json' with open(filename, encoding='utf-8') as data_file: data = json.loads(data_file.read()) return data def _parse_paths(self): main_file = self._napp_path / 'main.py' code = main_file.open().read() return self._parse_decorated_functions(code) def _parse_decorated_functions(self, code): """Return URL rule, HTTP methods and docstring.""" matches = re.finditer(r""" # @rest decorators (?P (?:@rest\(.+?\)\n)+ # one or more @rest decorators inside ) # docstring delimited by 3 double quotes .+?"{3}(?P.+?)"{3} """, code, re.VERBOSE | re.DOTALL) for function_match in matches: m_dict = function_match.groupdict() self._parse_docstring(m_dict['docstring']) self._add_function_paths(m_dict['decorators']) def _get_absolute_rule(self, rule): napp_prefix = "/api/{username}/{name}/" relative_rule = rule[1:] if rule.startswith('/') else rule return napp_prefix.format_map(self._napp_dict) + relative_rule def _add_function_paths(self, decorators_str): for rule, parsed_methods in self._parse_decorators(decorators_str): absolute_rule = self._get_absolute_rule(rule) path_url = self._rule2path(absolute_rule) path_methods = self._paths.setdefault(path_url, {}) self._add_methods(parsed_methods, path_methods) def _parse_docstring(self, docstring): """Parse the method docstring.""" match = re.match(r""" # Following PEP 257 \s* (?P[^\n]+?) \s* # First line ( # Description and YAML are optional (\n \s*){2} # Blank line # Description (optional) ( (?!-{3,}) # Don't use YAML as description \s* (?P.+?) \s* # Third line and maybe others (?=-{3,})? # Stop if "---" is found )? # YAML spec (optional) **currently not used** ( -{3,}\n # "---" begins yaml spec (?P.+) )? )? $""", docstring, re.VERBOSE | re.DOTALL) summary = 'TODO write the summary.' description = 'TODO write/remove the description' if match: m_dict = match.groupdict() summary = m_dict['summary'] if m_dict['description']: description = re.sub(r'(\s|\n){2,}', ' ', m_dict['description']) self._summary = summary self._description = description def _parse_decorators(self, decorators_str): matches = re.finditer(r""" @rest\( ## Endpoint rule (?P['"]) # inside single or double quotes (?P.+?) (?P=quote) ## HTTP methods (optional) (\s*,\s* methods=(?P\[.+?\]) )? .*?\)\s*$ """, decorators_str, re.VERBOSE) for match in matches: rule = match.group('rule') methods = self._parse_methods(match.group('methods')) yield rule, methods @classmethod def _parse_methods(cls, list_string): """Return HTTP method list. Use json for security reasons.""" if list_string is None: return ('GET',) # json requires double quotes json_list = list_string.replace("'", '"') return json.loads(json_list) def _add_methods(self, methods, path_methods): for method in methods: path_method = dict(summary=self._summary, description=self._description) path_methods[method.lower()] = path_method @classmethod def _rule2path(cls, rule): """Convert relative Flask rule to absolute OpenAPI path.""" typeless = re.sub(r'<\w+?:', '<', rule) # remove Flask types return typeless.replace('<', '{').replace('>', '}') # <> -> {} def _read_napp_info(self): filename = self._napp_path / 'kytos.json' return json.load(filename.open()) def _save(self, context): tpl_env = Environment( loader=FileSystemLoader(str(self._template.parent)), trim_blocks=True) content = tpl_env.get_template( 'openapi.yml.template').render(context) with self._api_file.open('w') as openapi: openapi.write(content) kytos-utils-2019.2/kytos/utils/napps.py0000644000175100001630000005012513577164155020743 0ustar runnerdocker00000000000000"""Manage Network Application files.""" import json import logging import os import pathlib import re import sys import tarfile import urllib from http import HTTPStatus # Disable pylint import checks that conflict with isort # pylint: disable=ungrouped-imports,wrong-import-order import pathspec from jinja2 import Environment, FileSystemLoader from ruamel.yaml import YAML from kytos.utils.client import NAppsClient from kytos.utils.config import KytosConfig from kytos.utils.exceptions import KytosException from kytos.utils.openapi import OpenAPI from kytos.utils.settings import SKEL_PATH LOG = logging.getLogger(__name__) # pylint: disable=too-many-instance-attributes,too-many-public-methods class NAppsManager: """Deal with NApps at filesystem level and ask Kytos to (un)load NApps.""" _NAPP_ENABLE = "api/kytos/core/napps/{}/{}/enable" _NAPP_DISABLE = "api/kytos/core/napps/{}/{}/disable" _NAPP_INSTALL = "api/kytos/core/napps/{}/{}/install" _NAPP_UNINSTALL = "api/kytos/core/napps/{}/{}/uninstall" _NAPPS_INSTALLED = "api/kytos/core/napps_installed" _NAPPS_ENABLED = "api/kytos/core/napps_enabled" _NAPP_METADATA = "api/kytos/core/napps/{}/{}/metadata/{}" def __init__(self): """Instance a new NAppsManager. This method do not need parameters. """ self._config = KytosConfig().config self._kytos_api = self._config.get('kytos', 'api') self.user = None self.napp = None self.version = None # Automatically get from kytosd API when needed self.__local_enabled = None self.__local_installed = None @property def _enabled(self): if self.__local_enabled is None: self.__require_kytos_config() return self.__local_enabled @property def _installed(self): if self.__local_installed is None: self.__require_kytos_config() return self.__local_installed def __require_kytos_config(self): """Set path locations from kytosd API. It should not be called directly, but from properties that require a running kytosd instance. """ if self.__local_enabled is None: uri = self._kytos_api + 'api/kytos/core/config/' try: ops = json.loads(urllib.request.urlopen(uri).read()) except urllib.error.URLError as err: msg = f'Error connecting to Kytos daemon: {uri} {err.reason}' print(msg) sys.exit(1) self.__local_enabled = pathlib.Path(ops.get('napps')) self.__local_installed = pathlib.Path(ops.get('installed_napps')) def set_napp(self, user, napp, version=None): """Set info about NApp. Args: user (str): NApps Server username. napp (str): NApp name. version (str): NApp version. """ self.user = user self.napp = napp self.version = version or 'latest' @property def napp_id(self): """Return a Identifier of NApp.""" return '/'.join((self.user, self.napp)) @staticmethod def _get_napps(napps_dir): """List of (username, napp_name) found in ``napps_dir``. Ex: [('kytos', 'of_core'), ('kytos', 'of_lldp')] """ jsons = napps_dir.glob('*/*/kytos.json') return sorted(j.parts[-3:-1] for j in jsons) def get_enabled_local(self): """Sorted list of (username, napp_name) of enabled napps.""" return self._get_napps(self._enabled) def get_installed_local(self): """Sorted list of (username, napp_name) of installed napps.""" return self._get_napps(self._installed) def get_enabled(self): """Sorted list of (username, napp_name) of enabled napps.""" uri = self._kytos_api + self._NAPPS_ENABLED try: response = urllib.request.urlopen(uri) if response.getcode() != 200: msg = "Error calling Kytos to check enabled NApps." raise KytosException(msg) content = json.loads(response.read()) return sorted((c[0], c[1]) for c in content['napps']) except urllib.error.URLError as exception: LOG.error("Error checking installed NApps. Is Kytos running?") raise KytosException(exception) def get_installed(self): """Sorted list of (username, napp_name) of installed napps.""" uri = self._kytos_api + self._NAPPS_INSTALLED try: response = urllib.request.urlopen(uri) if response.getcode() != 200: msg = "Error calling Kytos to check installed NApps." raise KytosException(msg) content = json.loads(response.read()) return sorted((c[0], c[1]) for c in content['napps']) except urllib.error.URLError as exception: LOG.error("Error checking installed NApps. Is Kytos running?") raise KytosException(exception) def is_installed(self): """Whether a NApp is installed.""" return (self.user, self.napp) in self.get_installed() def get_disabled(self): """Sorted list of (username, napp_name) of disabled napps. The difference of installed and enabled. """ installed = set(self.get_installed()) enabled = set(self.get_enabled()) return sorted(installed - enabled) def dependencies(self, user=None, napp=None): """Get napp_dependencies from install NApp. Args: user(string) A Username. napp(string): A NApp name. Returns: napps(list): List with tuples with Username and NApp name. e.g. [('kytos'/'of_core'), ('kytos/of_l2ls')] """ napps = self._get_napp_key('napp_dependencies', user, napp) return [tuple(napp.split('/')) for napp in napps] def get_description(self, user=None, napp=None): """Return the description from kytos.json.""" return self._get_napp_key('description', user, napp) def get_version(self, user=None, napp=None): """Return the version from kytos.json.""" return self._get_napp_key('version', user, napp) or 'latest' def _get_napp_key(self, key, user=None, napp=None): """Return a value from kytos.json. Args: user (string): A Username. napp (string): A NApp name key (string): Key used to get the value within kytos.json. Returns: meta (object): Value stored in kytos.json. """ if user is None: user = self.user if napp is None: napp = self.napp uri = self._kytos_api + self._NAPP_METADATA uri = uri.format(user, napp, key) meta = json.loads(urllib.request.urlopen(uri).read()) return meta[key] def disable(self): """Disable a NApp if it is enabled.""" uri = self._kytos_api + self._NAPP_DISABLE uri = uri.format(self.user, self.napp) try: json.loads(urllib.request.urlopen(uri).read()) except urllib.error.HTTPError as exception: if exception.code == HTTPStatus.BAD_REQUEST.value: LOG.error("NApp is not installed. Check the NApp list.") else: LOG.error("Error disabling the NApp") def enable(self): """Enable a NApp if not already enabled.""" uri = self._kytos_api + self._NAPP_ENABLE uri = uri.format(self.user, self.napp) try: json.loads(urllib.request.urlopen(uri).read()) except urllib.error.HTTPError as exception: if exception.code == HTTPStatus.BAD_REQUEST.value: LOG.error("NApp is not installed. Check the NApp list.") else: LOG.error("Error enabling the NApp") def enabled_dir(self): """Return the enabled dir from current napp.""" return self._enabled / self.user / self.napp def installed_dir(self): """Return the installed dir from current napp.""" return self._installed / self.user / self.napp def is_enabled(self): """Whether a NApp is enabled.""" return (self.user, self.napp) in self.get_enabled() def remote_uninstall(self): """Delete code inside NApp directory, if existent.""" uri = self._kytos_api + self._NAPP_UNINSTALL uri = uri.format(self.user, self.napp) try: json.loads(urllib.request.urlopen(uri).read()) except urllib.error.HTTPError as exception: if exception.code == HTTPStatus.BAD_REQUEST.value: LOG.error("Check if the NApp is installed.") else: LOG.error("Error uninstalling the NApp") @staticmethod def valid_name(username): """Check the validity of the given 'name'. The following checks are done: - name starts with a letter - name contains only letters, numbers or underscores """ return username and re.match(r'[a-zA-Z][a-zA-Z0-9_]{2,}$', username) @staticmethod def render_template(templates_path, template_filename, context): """Render Jinja2 template for a NApp structure.""" template_env = Environment( autoescape=False, trim_blocks=False, loader=FileSystemLoader(str(templates_path))) return template_env.get_template(str(template_filename)) \ .render(context) @staticmethod def search(pattern): """Search all server NApps matching pattern. Args: pattern (str): Python regular expression. """ def match(napp): """Whether a NApp metadata matches the pattern.""" # WARNING: This will change for future versions, when 'author' will # be removed. username = napp.get('username', napp.get('author')) strings = ['{}/{}'.format(username, napp.get('name')), napp.get('description')] + napp.get('tags') return any(pattern.match(string) for string in strings) napps = NAppsClient().get_napps() return [napp for napp in napps if match(napp)] def remote_install(self): """Ask kytos server to install NApp.""" uri = self._kytos_api + self._NAPP_INSTALL uri = uri.format(self.user, self.napp) try: json.loads(urllib.request.urlopen(uri).read()) except urllib.error.HTTPError as exception: if exception.code == HTTPStatus.BAD_REQUEST.value: LOG.error("NApp is not installed. Check the NApp list.") else: LOG.error("Error installing the NApp.") @classmethod def create_napp(cls, meta_package=False): """Bootstrap a basic NApp structure for you to develop your NApp. This will create, on the current folder, a clean structure of a NAPP, filling some contents on this structure. """ templates_path = SKEL_PATH / 'napp-structure/username/napp' ui_templates_path = os.path.join(templates_path, 'ui') username = None napp_name = None print('--------------------------------------------------------------') print('Welcome to the bootstrap process of your NApp.') print('--------------------------------------------------------------') print('In order to answer both the username and the napp name,') print('You must follow this naming rules:') print(' - name starts with a letter') print(' - name contains only letters, numbers or underscores') print(' - at least three characters') print('--------------------------------------------------------------') print('') while not cls.valid_name(username): username = input('Please, insert your NApps Server username: ') while not cls.valid_name(napp_name): napp_name = input('Please, insert your NApp name: ') description = input('Please, insert a brief description for your' 'NApp [optional]: ') if not description: # pylint: disable=fixme description = '# TODO: <<<< Insert your NApp description here >>>>' # pylint: enable=fixme context = {'username': username, 'napp': napp_name, 'description': description} #: Creating the directory structure (username/napp_name) os.makedirs(username, exist_ok=True) #: Creating ``__init__.py`` files with open(os.path.join(username, '__init__.py'), 'w') as init_file: init_file.write(f'"""Napps for the user {username}.""""') os.makedirs(os.path.join(username, napp_name)) #: Creating the other files based on the templates templates = os.listdir(templates_path) templates.remove('ui') templates.remove('openapi.yml.template') if meta_package: templates.remove('main.py.template') templates.remove('settings.py.template') for tmp in templates: fname = os.path.join(username, napp_name, tmp.rsplit('.template')[0]) with open(fname, 'w') as file: content = cls.render_template(templates_path, tmp, context) file.write(content) if not meta_package: NAppsManager.create_ui_structure(username, napp_name, ui_templates_path, context) print() print(f'Congratulations! Your NApp has been bootstrapped!\nNow you ' 'can go to the directory {username}/{napp_name} and begin to ' 'code your NApp.') print('Have fun!') @classmethod def create_ui_structure(cls, username, napp_name, ui_templates_path, context): """Create the ui directory structure.""" for section in ['k-info-panel', 'k-toolbar', 'k-action-menu']: os.makedirs(os.path.join(username, napp_name, 'ui', section)) templates = os.listdir(ui_templates_path) for tmp in templates: fname = os.path.join(username, napp_name, 'ui', tmp.rsplit('.template')[0]) with open(fname, 'w') as file: content = cls.render_template(ui_templates_path, tmp, context) file.write(content) @staticmethod def _check_module(folder): """Create module folder with empty __init__.py if it doesn't exist. Args: folder (pathlib.pathlib.Path): Module path. """ if not folder.exists(): folder.mkdir(parents=True, exist_ok=True, mode=0o755) (folder / '__init__.py').touch() @staticmethod def build_napp_package(napp_name): """Build the .napp file to be sent to the napps server. Args: napp_identifier (str): Identifier formatted as / Return: file_payload (binary): The binary representation of the napp package that will be POSTed to the napp server. """ def get_matches(path): """Return all NApp files matching any .gitignore pattern.""" ignored_files = [".git"] with open(".gitignore", 'r') as kytosignore: ignored_files.extend(kytosignore.readlines()) # Define Wildmatch pattern (default gitignore pattern) pattern = pathspec.patterns.GitWildMatchPattern spec = pathspec.PathSpec.from_lines(pattern, ignored_files) # Get tree containing all matching files match_tree = spec.match_tree(path) # Create list with all absolute paths of match tree return ["%s/%s" % (path, match) for match in match_tree] files = [] path = os.getcwd() for dir_file in os.walk(path): dirname, _, arc = dir_file files.extend([os.path.join(dirname, f) for f in arc]) # Allow the user to run `kytos napps upload` from outside the # napp directory. # Filter the files with the napp_name in their path # Example: home/user/napps/kytos/, napp_name = kronos # This filter will get all files from: # home/user/napps/kytos/kronos/* files = list(filter(lambda x: napp_name in x, files)) matches = get_matches(path) for filename in files.copy(): if filename in matches: files.remove(filename) # Create the '.napp' package napp_file = tarfile.open(napp_name + '.napp', 'x:xz') for local_f in files: # Add relative paths instead of absolute paths napp_file.add(pathlib.PurePosixPath(local_f).relative_to(path)) napp_file.close() # Get the binary payload of the package file_payload = open(napp_name + '.napp', 'rb') # remove the created package from the filesystem os.remove(napp_name + '.napp') return file_payload @staticmethod def create_metadata(*args, **kwargs): # pylint: disable=unused-argument """Generate the metadata to send the napp package.""" json_filename = kwargs.get('json_filename', 'kytos.json') readme_filename = kwargs.get('readme_filename', 'README.rst') ignore_json = kwargs.get('ignore_json', False) metadata = {} if not ignore_json: try: with open(json_filename) as json_file: metadata = json.load(json_file) except FileNotFoundError: print("ERROR: Could not access kytos.json file.") sys.exit(1) try: with open(readme_filename) as readme_file: metadata['readme'] = readme_file.read() except FileNotFoundError: metadata['readme'] = '' try: yaml = YAML(typ='safe') openapi_dict = yaml.load(pathlib.Path('openapi.yml').open()) openapi = json.dumps(openapi_dict) except FileNotFoundError: openapi = '' metadata['OpenAPI_Spec'] = openapi return metadata def upload(self, *args, **kwargs): """Create package and upload it to NApps Server. Raises: FileNotFoundError: If kytos.json is not found. """ self.prepare() metadata = self.create_metadata(*args, **kwargs) package = self.build_napp_package(metadata.get('name')) NAppsClient().upload_napp(metadata, package) def delete(self): """Delete a NApp. Raises: requests.HTTPError: When there's a server error. """ client = NAppsClient(self._config) client.delete(self.user, self.napp) @classmethod def prepare(cls): """Prepare NApp to be uploaded by creating openAPI skeleton.""" if cls._ask_openapi(): napp_path = pathlib.Path() tpl_path = SKEL_PATH / 'napp-structure/username/napp' OpenAPI(napp_path, tpl_path).render_template() print('Please, update your openapi.yml file.') sys.exit() @staticmethod def _ask_openapi(): """Return whether we should create a (new) skeleton.""" if pathlib.Path('openapi.yml').exists(): question = 'Override local openapi.yml with a new skeleton? (y/N) ' default = False else: question = 'Do you have REST endpoints and wish to create an API' \ ' skeleton in openapi.yml? (Y/n) ' default = True while True: answer = input(question) if answer == '': return default if answer.lower() in ['y', 'yes']: return True if answer.lower() in ['n', 'no']: return False def reload(self, napps=None): """Reload a NApp or all NApps. Args: napps (list): NApp list to be reloaded. Raises: requests.HTTPError: When there's a server error. """ client = NAppsClient(self._config) client.reload_napps(napps) # pylint: enable=too-many-instance-attributes,too-many-public-methods kytos-utils-2019.2/kytos/utils/users.py0000644000175100001630000001155313577164155020765 0ustar runnerdocker00000000000000"""Module used to handle Users in Napps Server.""" import logging import re from getpass import getpass from kytos.utils.client import UsersClient LOG = logging.getLogger(__name__) NAME_PATTERN = ("\t- insert only letters", r'[a-zA-Z][a-zA-Z]{2,}$') USERNAME_PATTERN = ("\t- start with letter\n" "\t- insert only numbers and letters", r'[a-zA-Z][a-zA-Z0-9_]{2,}$') PASSWORD_PATTERN = ("\t- insert only the caracters:" " [letters, numbers, _, %, &, -, $]" "\n\t- must be at least 6 characters", r'[a-zA-Z0-9_%\-&$]{6,}$') EMAIL_PATTERN = ("\t- follow the format: @\n" "\t\te.g. john@test.com", r'[^@]+@[^@]+\.[^@]+') PHONE_PATTERN = ("\t- insert only numbers", r'\d*$') class UsersManager: """Class used to handle users stored by Napps server.""" attributes = { "username": {"field_name": "Username (Required)", "pattern": USERNAME_PATTERN}, "first_name": {"field_name": "First Name (Required)", "pattern": NAME_PATTERN}, "last_name": {"field_name": "Last Name", "pattern": NAME_PATTERN}, "password": {"field_name": "Password (Required)", "pattern": PASSWORD_PATTERN}, "email": {"field_name": "Email (Required)", "pattern": EMAIL_PATTERN}, "phone": {"field_name": "Phone", "pattern": PHONE_PATTERN}, "city": {"field_name": "City", "pattern": NAME_PATTERN}, "state": {"field_name": "State", "pattern": NAME_PATTERN}, "country": {"field_name": "Country", "pattern": NAME_PATTERN} } required = ["username", "first_name", "password", "email"] def __init__(self): """Instance a new UsersManager. This method do not need parameters. """ self._users_client = UsersClient() def register(self): """Register a new user. This method will ask for user attributes and create the user in Napps server, when All required fields is filled. Returns: result(string): Response of user registration process. """ user = {} print('--------------------------------------------------------------') print('Welcome to the user registration process.') print('--------------------------------------------------------------') print("To continue you must fill the following fields.") for attribute, value in self.attributes.items(): is_required = attribute in self.required field_name = value['field_name'] pattern = value['pattern'] if attribute != 'password': user[attribute] = self.ask_question(field_name, pattern, is_required) else: user[attribute] = self.ask_question(field_name, pattern, password=True) return self._users_client.register(user) def ask_question(self, field_name, pattern=NAME_PATTERN, is_required=False, password=False): """Ask a question and get the input values. This method will validade the input values. Args: field_name(string): Field name used to ask for input value. pattern(tuple): Pattern to validate the input value. is_required(bool): Boolean value if the input value is required. password(bool): Boolean value to get input password with mask. Returns: input_value(string): Input value validated. """ input_value = "" question = ("Insert the field using the pattern below:" "\n{}\n{}: ".format(pattern[0], field_name)) while not input_value: input_value = getpass(question) if password else input(question) if not (input_value or is_required): break if password: confirm_password = getpass('Confirm your password: ') if confirm_password != input_value: print("Password does not match") input_value = "" if not self.valid_attribute(input_value, pattern[1]): error_message = "The content must fit the pattern: {}\n" print(error_message.format(pattern[0])) input_value = "" return input_value @classmethod def valid_attribute(cls, attribute, pattern): """Check the validity of the given 'attribute' using the given pattern. Args: attribute(string): String with the value of an attribute pattern(string): Pattern used to validate the attribute value. Returns: pattern_found(bool): Return True if the pattern match. """ return attribute and re.match(pattern, attribute) kytos-utils-2019.2/kytos/utils/decorators.py0000644000175100001630000000515213577164155021767 0ustar runnerdocker00000000000000"""Decorators for Kytos-utils.""" import logging import os import sys from getpass import getpass import requests from kytos.utils.config import KytosConfig LOG = logging.getLogger(__name__) # This class is used as decorator, so this class name is lowercase and the # invalid-name warning from pylint is disabled below. class kytos_auth: # pylint: disable=invalid-name """Class to be used as decorator to require authentication.""" def __init__(self, func): """Init method. Save the function on the func attribute and bootstrap a new config. """ self.func = func self.config = KytosConfig().config self.cls = None self.obj = None def __call__(self, *args, **kwargs): """Code run when func is called.""" if not (self.config.has_option('napps', 'api') and self.config.has_option('napps', 'repo')): uri = input("Enter the kytos napps server address: ") self.config.set('napps', 'api', os.path.join(uri, 'api', '')) self.config.set('napps', 'repo', os.path.join(uri, 'repo', '')) if not self.config.has_option('auth', 'user'): user = input("Enter the username: ") self.config.set('auth', 'user', user) else: user = self.config.get('auth', 'user') if not self.config.has_option('auth', 'token'): token = self.authenticate() else: token = self.config.get('auth', 'token') # Ignore private attribute warning. We don't wanna make it public only # because of a decorator. config = self.obj._config # pylint: disable=protected-access config.set('auth', 'user', user) config.set('auth', 'token', token) self.func.__call__(self.obj, *args, **kwargs) def __get__(self, instance, owner): """Deal with owner class.""" self.cls = owner self.obj = instance return self.__call__ def authenticate(self): """Check the user authentication.""" endpoint = os.path.join(self.config.get('napps', 'api'), 'auth', '') username = self.config.get('auth', 'user') password = getpass("Enter the password for {}: ".format(username)) response = requests.get(endpoint, auth=(username, password)) if response.status_code != 201: LOG.error(response.content) LOG.error('ERROR: %s: %s', response.status_code, response.reason) sys.exit(1) else: data = response.json() KytosConfig().save_token(username, data.get('hash')) return data.get('hash') kytos-utils-2019.2/kytos/utils/metadata.py0000644000175100001630000000053113577164155021376 0ustar runnerdocker00000000000000"""Holds main metadata information about the project. The present metadata is intended to be used mainly on the setup. """ __version__ = '2019.2' __author__ = 'Kytos Team' __author_email__ = 'devel@lists.kytos.io' __license__ = 'MIT' __url__ = 'http://github.com/kytos/kytos-utils' __description__ = 'Command line utilities to use with Kytos.' kytos-utils-2019.2/kytos/utils/client.py0000644000175100001630000001244713577164155021105 0ustar runnerdocker00000000000000"""REST communication with NApps Server.""" # This file is part of kytos-utils. # # Copyright (c) 2016 by Kytos Team. # # Authors: # Beraldo Leal import json import logging import os import sys import requests from kytos.utils.config import KytosConfig from kytos.utils.decorators import kytos_auth from kytos.utils.exceptions import KytosException LOG = logging.getLogger(__name__) class CommonClient: """Generic class used to make request the NApps server.""" def __init__(self, config=None): """Set Kytos config.""" if config is None: config = KytosConfig().config self._config = config @staticmethod def make_request(endpoint, **kwargs): """Send a request to server.""" data = kwargs.get('json', []) package = kwargs.get('package', None) method = kwargs.get('method', 'GET') function = getattr(requests, method.lower()) try: if package: response = function(endpoint, data=data, files={'file': package}) else: response = function(endpoint, json=data) except requests.exceptions.ConnectionError: LOG.error("Couldn't connect to NApps server %s.", endpoint) sys.exit(1) return response class NAppsClient(CommonClient): """Client for the NApps Server.""" def get_napps(self): """Get all NApps from the server.""" endpoint = os.path.join(self._config.get('napps', 'api'), 'napps', '') res = self.make_request(endpoint) if res.status_code != 200: msg = 'Error getting NApps from server (%s) - %s' LOG.error(msg, res.status_code, res.reason) sys.exit(1) return json.loads(res.content.decode('utf-8'))['napps'] def get_napp(self, username, name): """Return napp metadata or None if not found.""" endpoint = os.path.join(self._config.get('napps', 'api'), 'napps', username, name, '') res = self.make_request(endpoint) if res.status_code == 404: # We need to know if NApp is not found return None if res.status_code != 200: msg = 'Error getting %s/%s from server: (%d) - %s' raise KytosException(msg % (username, name, res.status_code, res.reason)) return json.loads(res.content) def reload_napps(self, napps=None): """Reload a specific NApp or all Napps. Args: napp (list): NApp list to be reload. Raises: requests.HTTPError: When there's a server error. """ if napps is None: napps = [] api = self._config.get('kytos', 'api') endpoint = os.path.join(api, 'api', 'kytos', 'core', 'reload', 'all') response = self.make_request(endpoint) for napp in napps: api = self._config.get('kytos', 'api') endpoint = os.path.join(api, 'api', 'kytos', 'core', 'reload', napp[0], napp[1]) response = self.make_request(endpoint) if response.status_code != 200: raise KytosException('Error reloading the napp: Module not founded' ' or could not be imported') return response.content @kytos_auth def upload_napp(self, metadata, package): """Upload the napp from the current directory to the napps server.""" endpoint = os.path.join(self._config.get('napps', 'api'), 'napps', '') metadata['token'] = self._config.get('auth', 'token') response = self.make_request(endpoint, json=metadata, package=package, method="POST") if response.status_code != 201: KytosConfig().clear_token() LOG.error("%s: %s - %s", response.status_code, response.reason, response.content.decode('utf-8')) sys.exit(1) # WARNING: this will change in future versions, when 'author' will get # removed. username = metadata.get('username', metadata.get('author')) name = metadata.get('name') print("SUCCESS: NApp {}/{} uploaded.".format(username, name)) @kytos_auth def delete(self, username, napp): """Delete a NApp. Raises: requests.HTTPError: If 400 <= status < 600. """ api = self._config.get('napps', 'api') endpoint = os.path.join(api, 'napps', username, napp, '') content = {'token': self._config.get('auth', 'token')} response = self.make_request(endpoint, json=content, method='DELETE') response.raise_for_status() class UsersClient(CommonClient): """Client for the NApps Server.""" def register(self, user_dict): """Send an user_dict to NApps server using POST request. Args: user_dict(dict): Dictionary with user attributes. Returns: result(string): Return the response of Napps server. """ endpoint = os.path.join(self._config.get('napps', 'api'), 'users', '') res = self.make_request(endpoint, method='POST', json=user_dict) return res.content.decode('utf-8') kytos-utils-2019.2/kytos/utils/config.py0000644000175100001630000001351013577164155021064 0ustar runnerdocker00000000000000"""Kytos utils configuration.""" # This file is part of kytos-utils. # # Copyright (c) 2016 Kytos Team # # Authors: # Beraldo Leal import json import logging import os import re import urllib.request from collections import namedtuple from configparser import ConfigParser LOG = logging.getLogger(__name__) class KytosConfig(): """Kytos Configs. Read the config file for kytos utils and/or request data for the user in order to get the correct paths and links. """ def __init__(self, config_file='~/.kytosrc'): """Init method. Receive the config_file as argument. """ self.config_file = os.path.expanduser(config_file) self.debug = False if self.debug: LOG.setLevel(logging.DEBUG) # allow_no_value=True is used to keep the comments on the config file. self.config = ConfigParser(allow_no_value=True) # Parse the config file. If no config file was found, then create some # default sections on the config variable. self.config.read(self.config_file) self.check_sections(self.config) self.set_env_or_defaults() if not os.path.exists(self.config_file): LOG.warning("Config file %s not found.", self.config_file) LOG.warning("Creating a new empty config file.") with open(self.config_file, 'w') as output_file: os.chmod(self.config_file, 0o0600) self.config.write(output_file) def log_configs(self): """Log the read configs if debug is enabled.""" for sec in self.config.sections(): LOG.debug(' %s: %s', sec, self.config.options(sec)) def set_env_or_defaults(self): """Read some environment variables and set them on the config. If no environment variable is found and the config section/key is empty, then set some default values. """ option = namedtuple('Option', ['section', 'name', 'env_var', 'default_value']) options = [option('auth', 'user', 'NAPPS_USER', None), option('auth', 'token', 'NAPPS_TOKEN', None), option('napps', 'api', 'NAPPS_API_URI', 'https://napps.kytos.io/api/'), option('napps', 'repo', 'NAPPS_REPO_URI', 'https://napps.kytos.io/repo'), option('kytos', 'api', 'KYTOS_API', 'http://localhost:8181/')] for option in options: if not self.config.has_option(option.section, option.name): env_value = os.environ.get(option.env_var, option.default_value) if env_value: self.config.set(option.section, option.name, env_value) self.config.set('global', 'debug', str(self.debug)) @staticmethod def check_sections(config): """Create a empty config file.""" default_sections = ['global', 'auth', 'napps', 'kytos'] for section in default_sections: if not config.has_section(section): config.add_section(section) def save_token(self, user, token): """Save the token on the config file.""" self.config.set('auth', 'user', user) self.config.set('auth', 'token', token) # allow_no_value=True is used to keep the comments on the config file. new_config = ConfigParser(allow_no_value=True) # Parse the config file. If no config file was found, then create some # default sections on the config variable. new_config.read(self.config_file) self.check_sections(new_config) new_config.set('auth', 'user', user) new_config.set('auth', 'token', token) filename = os.path.expanduser(self.config_file) with open(filename, 'w') as out_file: os.chmod(filename, 0o0600) new_config.write(out_file) def clear_token(self): """Clear Token information on config file.""" # allow_no_value=True is used to keep the comments on the config file. new_config = ConfigParser(allow_no_value=True) # Parse the config file. If no config file was found, then create some # default sections on the config variable. new_config.read(self.config_file) self.check_sections(new_config) new_config.remove_option('auth', 'user') new_config.remove_option('auth', 'token') filename = os.path.expanduser(self.config_file) with open(filename, 'w') as out_file: os.chmod(filename, 0o0600) new_config.write(out_file) @classmethod def get_metadata(cls): """Return kytos-utils metadata.""" meta_path = ("%s/metadata.py" % os.path.dirname(__file__)) meta_file = open(meta_path).read() metadata = dict(re.findall(r"(__[a-z]+__)\s*=\s*'([^']+)'", meta_file)) return metadata @classmethod def get_remote_metadata(cls): """Return kytos metadata.""" kytos_api = KytosConfig().config.get('kytos', 'api') meta_uri = kytos_api + 'api/kytos/core/metadata/' meta_file = urllib.request.urlopen(meta_uri).read() metadata = json.loads(meta_file) return metadata @classmethod def check_versions(cls): """Check if kytos and kytos-utils metadata are compatible.""" kytos_metadata = cls.get_remote_metadata() kutils_metadata = cls.get_metadata() kytos_version = kytos_metadata.get('__version__') kutils_version = kutils_metadata.get('__version__') if kytos_version != kutils_version: logger = logging.getLogger() logger.warning('kytos (%s) and kytos utils (%s) versions ' 'are not equal.', kytos_version, kutils_version) kytos-utils-2019.2/kytos/utils/settings.py0000644000175100001630000000027413577164155021462 0ustar runnerdocker00000000000000"""Module to include all constants in kytos-utils.""" import os from pathlib import Path BASE_ENV = Path(os.environ.get('VIRTUAL_ENV', '/')) SKEL_PATH = BASE_ENV / Path('etc/kytos/skel') kytos-utils-2019.2/PKG-INFO0000644000175100001630000000036713577164176016042 0ustar runnerdocker00000000000000Metadata-Version: 1.0 Name: kytos-utils Version: 2019.2 Summary: Command line utilities to use with Kytos. Home-page: http://github.com/kytos/kytos-utils Author: Kytos Team Author-email: UNKNOWN License: MIT Description: UNKNOWN Platform: UNKNOWN kytos-utils-2019.2/requirements/0000755000175100001630000000000013577164176017462 5ustar runnerdocker00000000000000kytos-utils-2019.2/requirements/dev.txt0000644000175100001630000000206713577164155021003 0ustar runnerdocker00000000000000# # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file=requirements/dev.txt requirements/dev.in # astroid==2.0.4 # via pylint click==7.0 # via pip-tools coverage==4.5.1 docopt==0.6.2 # via yala importlib-metadata==1.3.0 # via pluggy isort==4.3.4 # via pylint, yala lazy-object-proxy==1.3.1 # via astroid mccabe==0.6.1 # via pylint more-itertools==8.0.2 # via zipp pathspec==0.6.0 pip-tools==3.0.0 pluggy==0.12.0 # via tox py==1.6.0 # via tox pycodestyle==2.4.0 # via yala pydocstyle==2.1.1 # via yala pylint==2.1.1 # via yala six==1.12.0 # via astroid, pip-tools, pydocstyle, tox snowballstemmer==1.2.1 # via pydocstyle toml==0.10.0 # via tox tox==3.4.0 virtualenv==16.0.0 # via tox wrapt==1.10.11 # via astroid yala==1.7.0 zipp==0.6.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # setuptools kytos-utils-2019.2/requirements/run.in0000644000175100001630000000024513577164155020614 0ustar runnerdocker00000000000000docopt pathspec requests jinja2>=2.9.5 ruamel.yaml kytos-utils-2019.2/requirements/run.txt0000644000175100001630000000072513577164155021030 0ustar runnerdocker00000000000000# # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file=requirements/run.txt requirements/run.in # certifi==2019.6.16 # via requests chardet==3.0.4 # via requests docopt==0.6.2 idna==2.8 # via requests jinja2==2.10.1 markupsafe==1.1.1 # via jinja2 pathspec==0.6.0 requests==2.22.0 ruamel.yaml.clib==0.1.2 # via ruamel.yaml ruamel.yaml==0.16.5 urllib3==1.25.3 # via requests kytos-utils-2019.2/requirements/dev.in0000644000175100001630000000004513577164155020564 0ustar runnerdocker00000000000000coverage pathspec pip-tools tox yala kytos-utils-2019.2/etc/0000755000175100001630000000000013577164176015512 5ustar runnerdocker00000000000000kytos-utils-2019.2/etc/kytos/0000755000175100001630000000000013577164176016663 5ustar runnerdocker00000000000000kytos-utils-2019.2/etc/kytos/skel/0000755000175100001630000000000013577164176017621 5ustar runnerdocker00000000000000kytos-utils-2019.2/etc/kytos/skel/napp-structure/0000755000175100001630000000000013577164176022615 5ustar runnerdocker00000000000000kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/0000755000175100001630000000000013577164176024434 5ustar runnerdocker00000000000000kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/__init__.py0000644000175100001630000000004413577164155026540 0ustar runnerdocker00000000000000"""Module with NApp directories.""" kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/0000755000175100001630000000000013577164176025372 5ustar runnerdocker00000000000000kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/kytos.json.template0000644000175100001630000000032213577164155031242 0ustar runnerdocker00000000000000{ "author": "{{username}}", "username": "{{username}}", "name": "{{napp}}", "description": "{{description}}", "version": "1.0", "napp_dependencies": [], "license": "", "tags": [], "url": "" } kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/ui/0000755000175100001630000000000013577164176026007 5ustar runnerdocker00000000000000kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/ui/README.rst.template0000644000175100001630000000072213577164155031306 0ustar runnerdocker00000000000000Overview ======== Sometimes a NApp may require user interaction through the graphical interface (not just through the REST API). Kytos has its own `Vue.js`-based components (`Vue.js `_), that you can use on the interface. Plain HTML code is also supported. However, we strongly recommend that you stick to Kytos native (``k-*``) components. For more information visit the `documentation page`_. .. _documentation page: http://docs.kytos.io/ kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/__init__.py.template0000644000175100001630000000006213577164155031310 0ustar runnerdocker00000000000000"""Module for the NApp {{username}}/{{napp}}.""" kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/openapi.yml.template0000644000175100001630000000300713577164155031357 0ustar runnerdocker00000000000000openapi: 3.0.0 info: title: {{napp.username}}/{{napp.name}} version: {{napp.version}} description: {{napp.description}} paths: {% for path, methods in paths.items() %} {{path}}: {% for method, method_info in methods.items() %} {{method}}: summary: {{method_info.summary}} description: {{method_info.description}} parameters: # If you have parameters in the URL - name: Parameter's name as in path. required: true description: Describe parameter here in: path {% if method == "post" %} requestBody: content: application/json: schema: properties: # What the user should post dpid: # "dpid" is just an example. Replace it. type: string description: Switch datapath ID. example: 00:...:01 {% endif %} responses: 200: # You can add more responses description: Describe a successful call. content: application/json: # You can also use text/plain, for example schema: type: object # Adapt to your response properties: prop_one: type: string description: Meaning of prop_one example: an example of prop_one second_prop: type: integer description: Meaning of second_prop example: 42 {% endfor %} {% endfor %} kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/settings.py.template0000644000175100001630000000010413577164155031406 0ustar runnerdocker00000000000000"""Module with the Constants used in the {{username}}/{{napp}}.""" kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/.gitignore.template0000644000175100001630000000004213577164155031165 0ustar runnerdocker00000000000000.swp .pyc .napp __pycache__ .tox kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/main.py.template0000644000175100001630000000202113577164155030472 0ustar runnerdocker00000000000000"""Main module of {{username}}/{{napp}} Kytos Network Application. {{ description }} """ from kytos.core import KytosNApp, log from napps.{{username}}.{{napp}} import settings class Main(KytosNApp): """Main class of {{username}}/{{napp}} NApp. This class is the entry point for this napp. """ def setup(self): """Replace the '__init__' method for the KytosNApp subclass. The setup method is automatically called by the controller when your application is loaded. So, if you have any setup routine, insert it here. """ pass def execute(self): """Run after the setup method execution. You can also use this method in loop mode if you add to the above setup method a line like the following example: self.execute_as_loop(30) # 30-second interval. """ pass def shutdown(self): """Run when your napp is unloaded. If you have some cleanup procedure, insert it here. """ pass kytos-utils-2019.2/etc/kytos/skel/napp-structure/username/napp/README.rst.template0000644000175100001630000000007513577164155030672 0ustar runnerdocker00000000000000Overview ======== {{description}} Requirements ============ kytos-utils-2019.2/MANIFEST.in0000644000175100001630000000011613577164155016470 0ustar runnerdocker00000000000000include *.rst include LICENSE recursive-include etc/ * include requirements/* kytos-utils-2019.2/setup.cfg0000644000175100001630000000041013577164176016553 0ustar runnerdocker00000000000000[pycodestyle] exclude = .eggs,ENV,build,docs/conf.py,venv [yala] pylint args = --disable=too-few-public-methods,too-many-instance-attributes [pydocstyle] add-ignore = D105 [isort] known_first_party = kytos.utils,kytos.cli [egg_info] tag_build = tag_date = 0 kytos-utils-2019.2/kytos_utils.egg-info/0000755000175100001630000000000013577164176021022 5ustar runnerdocker00000000000000kytos-utils-2019.2/kytos_utils.egg-info/dependency_links.txt0000644000175100001630000000000113577164176025070 0ustar runnerdocker00000000000000 kytos-utils-2019.2/kytos_utils.egg-info/requires.txt0000644000175100001630000000027013577164176023421 0ustar runnerdocker00000000000000certifi==2019.6.16 chardet==3.0.4 docopt==0.6.2 idna==2.8 jinja2==2.10.1 markupsafe==1.1.1 pathspec==0.6.0 requests==2.22.0 ruamel.yaml.clib==0.1.2 ruamel.yaml==0.16.5 urllib3==1.25.3 kytos-utils-2019.2/kytos_utils.egg-info/PKG-INFO0000644000175100001630000000036713577164176022125 0ustar runnerdocker00000000000000Metadata-Version: 1.0 Name: kytos-utils Version: 2019.2 Summary: Command line utilities to use with Kytos. Home-page: http://github.com/kytos/kytos-utils Author: Kytos Team Author-email: UNKNOWN License: MIT Description: UNKNOWN Platform: UNKNOWN kytos-utils-2019.2/kytos_utils.egg-info/SOURCES.txt0000644000175100001630000000301713577164176022707 0ustar runnerdocker00000000000000AUTHORS.rst CHANGELOG.rst LICENSE MANIFEST.in README.rst setup.cfg setup.py bin/kytos etc/kytos/skel/napp-structure/username/__init__.py etc/kytos/skel/napp-structure/username/napp/.gitignore.template etc/kytos/skel/napp-structure/username/napp/README.rst.template etc/kytos/skel/napp-structure/username/napp/__init__.py.template etc/kytos/skel/napp-structure/username/napp/kytos.json.template etc/kytos/skel/napp-structure/username/napp/main.py.template etc/kytos/skel/napp-structure/username/napp/openapi.yml.template etc/kytos/skel/napp-structure/username/napp/settings.py.template etc/kytos/skel/napp-structure/username/napp/ui/README.rst.template kytos/__init__.py kytos/cli/__init__.py kytos/cli/commands/__init__.py kytos/cli/commands/napps/__init__.py kytos/cli/commands/napps/api.py kytos/cli/commands/napps/parser.py kytos/cli/commands/users/__init__.py kytos/cli/commands/users/api.py kytos/cli/commands/users/parser.py kytos/cli/commands/web/__init__.py kytos/cli/commands/web/api.py kytos/cli/commands/web/parser.py kytos/utils/__init__.py kytos/utils/client.py kytos/utils/config.py kytos/utils/decorators.py kytos/utils/exceptions.py kytos/utils/metadata.py kytos/utils/napps.py kytos/utils/openapi.py kytos/utils/settings.py kytos/utils/users.py kytos_utils.egg-info/PKG-INFO kytos_utils.egg-info/SOURCES.txt kytos_utils.egg-info/dependency_links.txt kytos_utils.egg-info/not-zip-safe kytos_utils.egg-info/requires.txt kytos_utils.egg-info/top_level.txt requirements/dev.in requirements/dev.txt requirements/run.in requirements/run.txtkytos-utils-2019.2/kytos_utils.egg-info/top_level.txt0000644000175100001630000000000613577164176023550 0ustar runnerdocker00000000000000kytos kytos-utils-2019.2/kytos_utils.egg-info/not-zip-safe0000644000175100001630000000000113577164176023250 0ustar runnerdocker00000000000000 kytos-utils-2019.2/README.rst0000644000175100001630000000534013577164155016425 0ustar runnerdocker00000000000000######## Overview ######## |Experimental| |Tag| |Release| |License| |Build| |Coverage| |Quality| This is a command line interface (cli) for `Kytos SDN Platform `_. With these utilities you can interact with Kytos daemon and manage Network Applications (NApps) on your controller. QuickStart ********** Installing ========== We use python3.6. So in order to use this software please install python3.6 into your environment beforehand. We are doing a huge effort to make Kytos and its components available on all common distros. So, we recommend you to download it from your distro repository. But if you are trying to test, develop or just want a more recent version of our software no problem: Download now, the latest release (it still a beta software), from our repository: First you need to clone *kytos-utils* repository: .. code-block:: shell $ git clone https://github.com/kytos/kytos-utils.git After cloning, the installation process is done by standard `setuptools` install procedure: .. code-block:: shell $ cd kytos-utils $ sudo python3.6 setup.py install Usage ***** In order to execute *kytos* command line, please run: .. code-block:: shell $ kytos --help Authors ******* For a complete list of authors, please open ``AUTHORS.rst`` file. Contributing ************ If you want to contribute to this project, please read `Kytos Documentation `__ website. License ******* This software is under *MIT-License*. For more information please read ``LICENSE`` file. .. |Experimental| image:: https://img.shields.io/badge/stability-experimental-orange.svg .. |Tag| image:: https://img.shields.io/github/tag/kytos/kytos-utils.svg :target: https://github.com/kytos/kytos-utils/tags .. |Release| image:: https://img.shields.io/github/release/kytos/kytos-utils.svg :target: https://github.com/kytos/kytos-utils/releases .. |Tests| image:: https://travis-ci.org/kytos/kytos-utils.svg?branch=master :target: https://travis-ci.org/kytos/kytos-utils .. |License| image:: https://img.shields.io/github/license/kytos/kytos-utils.svg :target: https://github.com/kytos/kytos-utils/blob/master/LICENSE .. |Build| image:: https://scrutinizer-ci.com/g/kytos/kytos-utils/badges/build.png?b=master :alt: Build status :target: https://scrutinizer-ci.com/g/kytos/kytos-utils/?branch=master .. |Coverage| image:: https://scrutinizer-ci.com/g/kytos/kytos-utils/badges/coverage.png?b=master :alt: Code coverage :target: https://scrutinizer-ci.com/g/kytos/kytos-utils/?branch=master .. |Quality| image:: https://scrutinizer-ci.com/g/kytos/kytos-utils/badges/quality-score.png?b=master :alt: Code-quality score :target: https://scrutinizer-ci.com/g/kytos/kytos-utils/?branch=master