class DeleteUserSubCommand(SubCommand): __command__ = 'delete' __aliases__ = ['d'] __help__ = 'Delete a user' __arguments__ = [ Argument('name', help='Username') ] def __call__(self, args): user = DBSession.query(User).filter(User.id == args.name).one_or_none() if user is None: print(f'Invalid username: {args.name}', file=sys.stderr) return 1 if input( f'Are you sure to delete the user: {user.id}? [N/y] ' ).casefold() != 'y'.casefold(): print(f'User: {args.name} is not deleted', file=sys.stderr) return 1 DBSession.delete(user) DBSession.commit()
class SetupCommand(SubCommand): __command__ = 'setup' __arguments__ = [ Argument('cidr', nargs='?', default=DEFAULT_CIDR, help=f'The network/mask (aka CIDR), default: {DEFAULT_CIDR}') ] def __call__(self, args): settings.cidr = args.cidr with open(args.configurationfilename, 'w') as f: f.write(settings.dumps()) ok(f'Settings are saved into {args.configurationfilename}') sshdfile = TextFile(SSHSERVER_CONFIGURATIONFILENAME) sshdfile.commentout('PermitTunnel (?!yes)') if not sshdfile.hasline('PermitTunnel yes'): sshdfile.append(SSHDSETTINGS) ok( f'The following lines are added into the ' \ f'{SSHSERVER_CONFIGURATIONFILENAME}' ) info(SSHDSETTINGS) warning('Do you want to restart the openssh-server?') if input('You may restart the SSH server manualy ' \ 'if you choose no[Y/n]:') in ('y', 'Y'): print('Restarting ssh server...') else: ok(f'PermitTunnel is already enabled in {sshdfile.filename}.') sshdfile.saveifneeded()
class MockupServer(SubCommand): __command__ = 'mockupserver' __help__ = 'Generates a mockup from YAML file.' __arguments__ = [ Argument('story', metavar='YAML', help='A story file'), ] def __call__(self, args): from nanohttp import Controller, HTTPNotFound, context from ..authoring import Story with open(args.story) as story_file: story = Story.load(story_file) class RootMockupController(Controller): def __init__(self): self.stories = [] def add_story(self, story): self.stories.append(story) def server(self, call): for k, v in call.response.headers: context.response_headers.add_header(k, v) yield call.response.text.encode() def __call__(self, *remaining_paths): calls = [story.base_call] + story.calls for call in calls: url = call.url.replace(':', '') if set(url.strip('/').split('/')) == set(remaining_paths): return self.server(call) raise HTTPNotFound() from nanohttp import quickstart quickstart(RootMockupController())
class Main(Root): __completion__ = True __arguments__ = [ Argument( '-c', '--configuration-file', metavar="FILE", dest='configurationfile', help='Configuration file', ), Serve, ] def __init__(self, application): self.application = application self.__help__ = f'{sys.argv[0]} command line interface.' self.__arguments__.extend(self.application.cliarguments) super().__init__() def _execute_subcommand(self, args): args.application = self.application if args.configurationfile: self.application.settings.loadfile(args.configurationfile) super()._execute_subcommand(args)
class Main(Root): __completion__ = True __arguments__ = [ Argument( '-c', '--configuration-file', metavar="FILE", dest='configurationfile', help='Configuration file', ), Serve, ] def __init__(self, application): if application.version: self.__arguments__.append( Argument('--version', action='store_true') ) self.application = application self.__help__ = f'{sys.argv[0]} command line interface.' self.__arguments__.extend(self.application.cliarguments) super().__init__() def _execute_subcommand(self, args): args.application = self.application if args.configurationfile: self.application.settings.loadfile(args.configurationfile) return super()._execute_subcommand(args) def __call__(self, args): if self.application.version and args.version: print(self.application.version) return self._parser.print_help()
class DocumentaryLauncher(SubCommand): __command__ = 'document' __help__ = 'Generates REST API Documentation from standard input to ' \ 'standard output.' __arguments__ = [ Argument('-f', '--format', default='markdown', help='The output format. One of markdown, html. Default is ' 'markdown.'), ] formatters = { 'markdown': MarkdownFormatter, # 'html': HtmlFormatter, } def __call__(self, args): self.convert_file(sys.stdin, sys.stdout, args.format) def convert_file(self, source, destination, format): from ..authoring import Story story = Story.load(source) story.document(destination, formatter_factory=self.formatters[format])
class PasswdSubCommand(SubCommand): __command__ = 'passwd' __help__ = 'change a user\'s password' __arguments__ = [ Argument( 'name', help='Username', ), ] def __call__(self, args): user = DBSession.query(User).filter(User.id == args.name).one_or_none() if user is None: print(f'Invalid username: {args.name}', file=sys.stderr) return 1 password=getpass('New Password:'******'Again:') if password != confirm: print(f'Passwords are mismatch.', file=sys.stderr) return 1 user.password=password DBSession.commit()
class WebsocketStartSubSubCommand(SubCommand): # pragma: no cover __help__ = 'Starts the websocket server.' __command__ = 'start' __arguments__ = [ Argument( '-b', '--bind', default=DEFAULT_ADDRESS, metavar='{HOST:}PORT', help='Bind Address. default: %s' % DEFAULT_ADDRESS, ), ] def __call__(self, args): host, port = args.bind.split(':')\ if ':' in args.bind else ('', args.bind) kw = {} if port: kw['port'] = port if host: kw['host'] = host web.run_app(app, **kw)
class StartSubSubCommand(SubCommand): __command__ = 'start' __help__ = 'Starts the background worker.' __arguments__ = [ Argument( '-g', '--gap', type=int, default=None, help='Gap between run next task.', ), Argument( '-s', '--status', default=[], action='append', help='Task status to process', ), Argument( '-n', '--number-of-threads', type=int, default=None, help='Number of working threads', ), ] def __call__(self, args): from restfulpy.taskqueue import worker signal.signal(signal.SIGINT, self.kill_signal_handler) signal.signal(signal.SIGTERM, self.kill_signal_handler) if not args.status: args.status = {'new'} if args.gap is not None: settings.worker.merge({'gap': args.gap}) print(f'The following task types would be processed with gap of ' f'{settings.worker.gap}s:') print('Tracking task status(es): %s' % ','.join(args.status)) number_of_threads = \ args.number_of_threads or settings.worker.number_of_threads for i in range(number_of_threads): t = threading.Thread(target=worker, name='restfulpy-worker-thread-%s' % i, daemon=True, kwargs=dict(statuses=args.status, filters=args.filter)) t.start() print('Worker started with %d threads' % number_of_threads) print('Press Ctrl+C to terminate worker') signal.pause() @staticmethod def kill_signal_handler(signal_number, frame): if signal_number == signal.SIGINT: print('You pressed Ctrl+C!') elif signal_number in (signal.SIGTERM, signal.SIGKILL): print('OS Killed Me!') sys.stdin.close() sys.stderr.close() sys.stdout.close() sys.exit(1)
class ScaffoldSubCommand(SubCommand): __command__ = 'scaffold' __help__ = 'Creates an empty boilerplate' __arguments__ = [ Argument( 'name', help='The snake_case project\'s name' ), Argument( 'author', help='The project\'s author' ), Argument( 'email', help='The projects author\'s email' ), Argument( '-t', '--template', default='full', help= \ f'The project\'s template, one of {", ".join(TEMPLATES)}. ' f'default: full.' ), Argument( '-o', '--overwrite', default=False, action='store_true', help= \ 'Continue and overwrite files when the target ' 'directory(-d/--directory) is not empty.' ), Argument( '-d', '--directory', default='.', help= \ 'Change to this directory before generating new files. It ' 'will make it if does not exists. default: "."' ), ] def __call__(self, args): template_path = path.join(TEMPLATES_PATH, args.template) target_path = path.abspath(args.directory) title_snakecase = args.name.lower() if not path.exists(template_path): print(f'Invalid template: {template_path}', file=sys.stderr) return 1 if not path.exists(target_path): os.makedirs(target_path) for top, subdirectories, subfiles in os.walk(template_path): current_directory = path.relpath(top, template_path) \ .replace('__project__', title_snakecase) for filename in subfiles: if not filename.endswith('.template'): continue source = path.abspath(path.join(top, filename)) target = path.abspath( path.join( target_path, current_directory, filename[:-9].replace('__project__', title_snakecase))) if not args.overwrite and path.exists(target): print(f'Target file exists: {target}, use -o to overwrite', file=sys.stderr) return 1 os.makedirs(path.dirname(target), exist_ok=True) self.install_file(source, target, args) if args.template == 'singlefile': os.chmod(target, 0o774) def install_file(self, source, target, args): print(f'Installing {target}') title_snakecase = args.name.lower() title_camelcase = to_pascal_case(title_snakecase) with open(source) as s, open(target, 'w') as t: content = s.read() content = content.replace('${project_snakecase}', title_snakecase) content = content.replace('${project_author}', args.author) content = content.replace('${project_author_email}', args.email) content = content.replace('${project_camelcase}', title_camelcase) content = content.replace('${restfulpy_version}', restfulpy.__version__) t.write(content)
class ConnectCommand(SubCommand): __command__ = 'connect' __aliases__ = ['c'] __arguments__ = [ Argument('-v', '--verbose', action='store_true', help='Verbose'), Argument('-r', '--retrymax', type=int, default=0, help='Maximum retry, default is: 0, infinite!'), ] def getdefaultgateway(self): """Returns the current default gateway from `/proc` """ with open('/proc/net/route') as fh: for line in fh: fields = line.strip().split() if fields[1] != '00000000' or not int(fields[3], 16) & 2: continue return socket.inet_ntoa(struct.pack("<L", int(fields[2], 16))) def connect(self, gateway, hostaddr, hostname, args): index = settings.index hostname = settings.hostname remoteuser = settings.remoteuser localuser = settings.localuser serveraddr = settings.addresses.server terminating = False c = args.retrymax infinite = c == 0 sshargs = [] if args.verbose: sshargs.append('-v') sshargs.append(f'-o"ServerAliveInterval 10"') sshargs.append(f'-o"ServerAliveCountMax 1"') sshargs.append(f'-o"ConnectTimeout 10"') sshargs.append(f'-o"ConnectionAttempts 20"') def term(sig, frame): nonlocal terminating terminating = True os.killpg(os.getpgid(sshprocess.pid), signal.SIGTERM) signal.signal(signal.SIGTERM, term) signal.signal(signal.SIGINT, term) while infinite or (c > 0): try: shell(f'ip route replace {hostaddr} via {gateway}') shell(f'ip route replace default via {serveraddr}') sshprocess = Popen( f'sudo -u {localuser} ssh {remoteuser}@{hostname} ' \ f'-Nw {index}:{index} {" ".join(sshargs)}', shell=True, preexec_fn=os.setsid, ) sshprocess.wait() if terminating or sshprocess.returncode <= 0: break finally: shell(f'ip route del {hostaddr} via {gateway}', check=False) shell(f'ip route replace default via {gateway}', check=False) time.sleep(1) c -= 1 def __call__(self, args): if USER != 'root': error('Please run this command as root.') return 1 hostname = settings.hostname remoteuser = settings.remoteuser localuser = settings.localuser index = settings.index ifname = f'tun{index}' clientaddr = settings.addresses.client serveraddr = settings.addresses.server netmask = settings.netmask hostaddr = socket.gethostbyname(hostname) gateway = self.getdefaultgateway() if gateway is None: error(f'Invalid default gateway: {gateway}') try: shell( f'ip tuntap add mode tun dev {ifname} user {localuser} group {localuser}' ) shell( f'ip address add dev {ifname} {clientaddr}/{netmask} ' \ f'peer {serveraddr}/{netmask}' ) shell(f'ip link set up dev {ifname}') self.connect(gateway, hostaddr, hostname, args) finally: info('Terminating...') shell(f'ip tuntap delete mode tun dev {ifname}', check=False)
class CP(Root): __help__ = 'easycli example' __command__ = 'cp' __completion__ = False __arguments__ = [ Argument( 'files', nargs='*', metavar='N', type=str, help='source path' ), Argument( '-r', '--recursive', action='store_true', help='copy directories recursively' ), Argument( '-v', '--verbose', help='explain what is being done', action='store_true', ), ] def _split_source_destination(self, args): if len(args.files) < 2: print('Source or destination is not entered') sys.exit() sources = args.files[:-1] destination = args.files[-1] return sources, destination def __call__(self, args): sources, destination = self._split_source_destination(args) current_dir = os.getcwd() for source_file in sources: if '/' in source_file[:-1]: source_path = source_file os.chdir(source_file[:source_file.rindex('/')]) file_name = source_file[source_file.rindex('/')+1:] else: source_path = current_dir + '/' + source_file file_name = source_file if not ( os.path.exists(source_path) or os.path.isfile(source_path) ): print( f"cp: cannot stat '{source_path}':"\ "No such file or directory" ) if os.path.isdir(source_path): if not args.recursive: destination = os.path.abspath(destination) destination = destination.split('/')[-1] print( f"cp: -r not specified;"\ "omitting directory {destination!r}" ) sys.exit() else: source = os.path.abspath(source_path) destination = os.path.abspath(destination) if '/' not in source[-1] or '/' not in source: top = destination + '/' + source[source.rindex('/')+1:] else: top = destination + '/' + source if not os.path.isdir(destination): os.mkdir(destination) if source == top: if '/' in destination: print( f"cp: '{source_path}' and ./'{destination}'" "are the same file" ) else: print( f"cp: '{source_path}' and '{destination}'" "are the same file" ) sys.exit() if os.path.exists(top): for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) os.rmdir(top) recursive_copy(source, destination, args.verbose) elif os.path.isfile(source_path): source = source_path destination = destination if not(os.path.exists(source)): print( f"cp: cannot stat '{source_path}':" "No such file or directory" ) if not os.path.isdir(destination): os.mkdir(destination) if destination == "": print( f"cp: missing destination file operand after " "'{destination}'" ) if os.path.isdir(source): print( f"cp: -r not specified;"\ "omitting directory '{source_path}'" ) copyfile(source, destination, args.verbose)
class ConnectCommand(SubCommand): __command__ = 'connect' __aliases__ = ['c'] __arguments__ = [ Argument('-v', '--verbose', action='store_true', help='Verbose'), Argument( '-i', '--aliveinterval', type=int, default=2, help=\ 'ssh ServerAliveInterval option, see man ssh_config. ' \ 'default is: 2' ), Argument( '-m', '--alivecountmax', type=int, default=1, help=\ 'ssh ServerAliveCountMax option, see man ssh_config. ' \ 'default is: 1' ), Argument( '-r', '--retrymax', type=int, default=0, help='Maximum retry, default is: 0, infinite!' ), Argument( '--connecttimeout', type=int, default=2, help='ssh ConnectTimeout option, default is: 2.' ), Argument( '--connectionattempts', type=int, default=2, help='ssh ConnectionAttempts option, default is: 2.' ) ] def getdefaultgateway(self): """Returns the current default gateway from `/proc` """ with open('/proc/net/route') as fh: for line in fh: fields = line.strip().split() if fields[1] != '00000000' or not int(fields[3], 16) & 2: continue return socket.inet_ntoa(struct.pack("<L", int(fields[2], 16))) def connect(self, gateway, hostaddr, hostname, args): index = settings.index hostname = settings.hostname remoteuser = settings.remoteuser localuser = settings.localuser serveraddr = settings.addresses.server c = args.retrymax infinite = c == 0 sshargs = [] if args.verbose: sshargs.append('-v') sshargs.append(f'-o"ServerAliveInterval {args.aliveinterval}"') sshargs.append(f'-o"ServerAliveCountMax {args.alivecountmax}"') sshargs.append(f'-o"ConnectTimeout {args.connecttimeout}"') sshargs.append(f'-o"ConnectionAttempts {args.connectionattempts}"') while infinite or (c > 0): try: shell(f'ip route replace {hostaddr} via {gateway}') shell(f'ip route replace default via {serveraddr}') shell( f'sudo -u {localuser} ssh {remoteuser}@{hostname} ' \ f'-Nw {index}:{index} {" ".join(sshargs)}' ) except CalledProcessError as ex: if ex.returncode < 0: break except: traceback.print_exc() time.sleep(3) finally: shell(f'ip route del {hostaddr} via {gateway}', check=False) shell(f'ip route replace default via {gateway}', check=False) time.sleep(1) c -= 1 def __call__(self, args): if USER != 'root': error('Please run this command as root.') return 1 hostname = settings.hostname remoteuser = settings.remoteuser localuser = settings.localuser index = settings.index ifname = f'tun{index}' clientaddr = settings.addresses.client serveraddr = settings.addresses.server netmask = settings.netmask hostaddr = socket.gethostbyname(hostname) gateway = self.getdefaultgateway() if gateway is None: error(f'Invalid default gateway: {gateway}') try: shell( f'ip tuntap add mode tun dev {ifname} user {localuser} group {localuser}' ) shell( f'ip address add dev {ifname} {clientaddr}/{netmask} ' \ f'peer {serveraddr}/{netmask}' ) shell(f'ip link set up dev {ifname}') self.connect(gateway, hostaddr, hostname, args) finally: info('Terminating...') shell(f'ip tuntap delete mode tun dev {ifname}', check=False)
class CP(Root): __help__ = 'easycli example' __command__ = 'cp' __completion__ = True __arguments__ = [ # Argument( # '-V', '--version', # action='version', # version='CP 1.0', # ), Argument('-r', '--recursive', action='store_true', help='copy directories recursively'), Argument( '-v', '--verbose', help='asdf', action='store_true', ), Argument('source', help='source path'), Argument('destination', help='destination path'), ] def __call__(self, args): if not (os.path.exists(args.source) or os.path.isfile(args.source)): print( f"cp: cannot stat '{args.source}': No such file or directory") if os.path.isdir(args.source): if not args.recursive: destination = os.path.abspath(args.destination) destination = destination.split('/')[-1] print( f"cp: -r not specified; omitting directory {destination!r}" ) sys.exit() else: source = os.path.abspath(args.source) destination = os.path.abspath(args.destination) if '/' not in source[-1] or '/' not in source: top = destination + '/' + source[source.rindex('/') + 1:] else: top = destination + '/' + source if not os.path.isdir(args.destination): os.mkdir(args.destination) if source == top: if '/' in destination: print(f"cp: '{args.source}' and ./'{destination}'" "are the same file") else: print(f"cp: '{args.source}' and '{args.destination}'" "are the same file") sys.exit() if os.path.exists(top): for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) os.rmdir(top) recursive_copy(source, destination, args.verbose) elif os.path.isfile(args.source): source = args.source destination = args.destination if not (os.path.exists(source)): print(f"cp: cannot stat '{args.source}':" "No such file or directory") if not os.path.isdir(args.destination): os.mkdir(args.destination) if destination == "": print(f"cp: missing destination file operand after " "'{args.destination}'") if os.path.isdir(source): print( f"cp: -r not specified; omitting directory '{args.source}'" ) copyfile(source, destination, args.verbose)