def do_deploy(self, args): """ Usage: deploy [--dev] <path> Deploys a recipe. Arguments: <path> A path to the local recipe or a git repo which contains a recipe. Options: --dev Uses git clone instead of git archive to keep .git. This should NOT be done on an assembly that are going to be released. """ try: self._rpc.do_deploy_reset_check() path = args['path'] if not giturlparse.validate(path): if not path.startswith("/"): path = os.path.join(os.getcwd(), path) rpc_put_recipe(self._rpc, path, should_skip=get_filter(os.path.join(path, ".jscignore"))) rec = self._rpc.do_deploy_read_new_recipe({"path": path}) state = recipe.run(self._rpc, rec, args['--dev']) args.update({"state": state}) self._rpc.do_deploy_finalize(args) except SshRpcCallError as e: log.white(str(e))
def complete_deploy(self, text, line, begidx, endidx): try: argv = shlex.split(line) cwd = os.getcwd() if len(argv) == 1 or (len(argv) == 2 and argv[1] == '--dev'): ret = os.listdir(cwd) else: parsed = docopt(self.do_deploy.__doc__, argv[1:]) path = parsed['<path>'] ret = [] if path == '.': ret.append('.') ret.append('..') elif path == '..': ret.append('..') elif os.path.isdir(path): os.chdir(path) elif os.path.isdir(os.path.dirname(path)): os.chdir(os.path.dirname(path)) resolved = glob.glob(os.path.basename(path)+'*') if len(resolved) == 1 and resolved[0] == path: pass else: ret += resolved os.chdir(cwd) return map(lambda d: d + "/" if os.path.isdir(d) else d, ret) except Exception as e: # The DocoptExit is thrown when the args do not match. # We print a message to the user and the usage block. log.white('Invalid Command!') log.white(e) return []
def print_status(assembly_id, status, env, verbose=False): email = env["ident"]["user"]["email"] name = env["ident"]["user"]["name"] if status['deploy_time'] is None: status['deploy_time'] = "<never>" log.white( u"\n".join([u"Jsc v{version} attached to assembly [{assembly_id}] by [{name} {email}]".format(version=__version__, assembly_id=assembly_id, name=name, email=email), u"{dir}: {used} used of {total} ({percent_used} used)".format(**status['code_usage']), u" deployed recipe: {recipe_name}".format(**status), u" at {deploy_time}".format(**status), u" total backups: {total_backups}".format(**status), u"{dir}: {used} used of {total} ({percent_used} used)".format(**status['state_usage'])])) if verbose: package_lines = [] if "software" in status: if "package" in status["software"]: for pkg in status['software']["package"]: pkg_kw = {"pkg": pkg, "ver": status['software']["package"][pkg]["version"]} package_lines.append("\t{pkg}: [{ver}]".format(**pkg_kw)) gd_lines = [] if "software" in status: if "gd" in status["software"]: for path in status['software']["gd"]: gd_kw = {"ref": status['software']["gd"][path]["ref"], "src": status['software']["gd"][path]["repo"], "commit": status['software']["gd"][path]["commit"][0:8], "path": path} gd_lines.append("\t{path}: [{src}] [{ref}] [{commit}]".format(**gd_kw)) all_lines = ["Deploiyed packages:"] all_lines += package_lines all_lines += ["Git deployed software:"] all_lines += gd_lines log.white("\n".join(all_lines))
def rpc_put_recipe(rpc, src, dst=NEW_RECIPE_SRC, chunk_size=2**16, should_skip=lambda x: False): def put_file(fn_path_src, fn_path_dst): f_stat = os.stat(fn_path_src) bytes_left = f_stat.st_size log.white("putting local:{src} -> remote:{dst}".format(src=fn_path_src, dst=dst)) with open(fn_path_src, "rb") as fo: while True: b64_content = base64.standard_b64encode(fo.read(chunk_size)) rpc.do_file_append({"path": fn_path_dst, "content": b64_content}) bytes_left -= chunk_size if bytes_left <= 0: break if os.path.isfile(src) or os.path.islink(src): put_file(src, dst + "/Jumpstart-Recipe") elif os.path.isdir(src): for fn in os.listdir(src): fn_path_src = os.path.join(src, fn) if should_skip(fn_path_src): continue fn_path_dst = dst + "/" + fn if os.path.islink(fn_path_src): link_target = os.readlink(fn_path_src) rpc.do_symlink({"path": fn_path_dst, "target": link_target}) elif os.path.isfile(fn_path_src): put_file(fn_path_src, fn_path_dst) elif os.path.isdir(fn_path_src): rpc.do_mkdir({"path": fn_path_dst}) rpc_put_recipe(rpc, fn_path_src, fn_path_dst, chunk_size, should_skip) else: log.white("could not find recipe dir or file")
def call(self, method, args): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) for port in range(14000, 62000): try: server_socket.bind(("127.0.0.1", port)) break except socket.error: continue server_socket.listen(5) ev = Event() input_thread = Process(target=input_reader, args=(port, ev)) input_thread.start() (input_socket, _) = server_socket.accept() input_socket.setblocking(0) rpc_cmd = self.rpc(method, args) self._sendall(rpc_cmd) recv_buf = "" try: while True: rl, _, xl = select.select([self.ssh_channel, input_socket], [], []) if self.ssh_channel in rl: if self.ssh_channel.recv_ready(): new_data = self.ssh_channel.recv(4096) recv_buf += new_data if "\n" in recv_buf: lines = recv_buf.split("\n") # Last line is either not complete or empty string. # ("x\nnot compl".split("\n") => ['x', 'not compl'] or "x\n".split("\n") => ['x', '']) # so we put it back in recv_buf for next iteration recv_buf = lines.pop() for line in lines: resp = json.loads(line) if "stdout" in resp: sys.stdout.write(resp["stdout"]) sys.stdout.flush() elif "stderr" in resp: log.white(resp["stderr"], f=sys.stderr) elif "result" in resp: if resp['error'] is not None: raise SshRpcCallError(resp['error']['message']) #print("ending",method) return resp["result"] if self.ssh_channel.recv_stderr_ready(): log.white("{}".format(self.ssh_channel.recv_stderr(4096))) if self.ssh_channel.exit_status_ready(): raise SshRpcError() if input_socket in rl: new_stdin_data = input_socket.recv(1024) self._sendall(self.stdin(new_stdin_data)) except (KeyboardInterrupt, SshRpcError): self.ssh_channel.shutdown(2) self.ssh_channel = None raise KeyboardInterrupt() finally: ev.set() input_thread.terminate() input_thread.join() del input_socket del server_socket
def do_env(self, args): """ Usage: env Dumps /app/env.json to console. """ env_json = self._rpc.do_env() log.white(json.dumps(env_json, sort_keys=True, indent=4, separators=(',', ': ')))
def do_sync(self, args): """ Usage: sync Syncs software list so it becomes visable in the developer panel. """ self._rpc.do_sync() log.white("sync done!")
def _open_channel(self): try: self.ssh_channel = self.ssh_transport.open_session() self.ssh_channel.setblocking(0) self.ssh_channel.exec_command('/tmp/server') self.stdout_file = self.ssh_channel.makefile("r", 0) except paramiko.ssh_exception.SSHException: log.white("Connection lost, make sure the assembly is running, then reconnect.") os._exit(1)
def put_file(fn_path_src, fn_path_dst): f_stat = os.stat(fn_path_src) bytes_left = f_stat.st_size log.white("putting local:{src} -> remote:{dst}".format(src=fn_path_src, dst=dst)) with open(fn_path_src, "rb") as fo: while True: b64_content = base64.standard_b64encode(fo.read(chunk_size)) rpc.do_file_append({"path": fn_path_dst, "content": b64_content}) bytes_left -= chunk_size if bytes_left <= 0: break
def do_revert(self, args): """ Usage: revert <id> Reverts a backup. This will destroy any changes you've made since backup. Arguments: <id> Id of the backup to restore. """ self._rpc.do_revert(args) log.white("revert of backup done!")
def fn(self, arg): try: opt = docopt(fn.__doc__, shlex.split(arg)) except DocoptExit as e: # The DocoptExit is thrown when the args do not match. # We print a message to the user and the usage block. log.white('Invalid Command!') log.white(e) return except SystemExit: # The SystemExit exception prints the usage for --help # We do not need to do the print here. return return func(self, {k.lstrip("<").rstrip(">"): opt[k] for k in opt})
def do_ssh(self, args): """ Usage: ssh Opens a new terminal and starts an SSH shell to this assembly. """ if sys.platform.startswith("linux"): cmd_open = "xdg-open" elif sys.platform.startswith("darwin"): cmd_open = "open" else: log.white("Cannot start ssh shell since your platform [{platform}] is not supported.".format(platform=platform)) return try: subprocess.check_call([cmd_open, "ssh://{uri}".format(uri=self._ssh_conn_str)]) except subprocess.CalledProcessError: pass
def do_backup(self, args): """ Usage: backup [ls] backup new backup du backup rm <id> Managing backups. Arguments: ls List backups. new Create a new backup. du Like ls, but also shows size and disk space usage. rm Removes backup with supplied id. """ resp = self._rpc.do_backup(args) if resp is not None: for line in resp: log.white(line)
def call(self, method, args): rpc_cmd = self.rpc(method, args) self._sendall(rpc_cmd) recv_buf = "" stdin_fd = os.dup(sys.stdin.fileno()) flags = fcntl.fcntl(stdin_fd, fcntl.F_GETFL, 0) flags |= os.O_NONBLOCK fcntl.fcntl(stdin_fd, fcntl.F_SETFL, flags) tty = os.fdopen(stdin_fd, "r", 0) try: # Some code stolen from getpass.py # getpass Authors: Piers Lauder (original) # Guido van Rossum (Windows support and cleanup) # Gregory P. Smith (tty support & GetPassWarning)b old = termios.tcgetattr(stdin_fd) # a copy to save new = termios.tcgetattr(stdin_fd) new[3] &= ~termios.ECHO # 3 == 'lflags' new[3] &= ~termios.ICANON # 3 == 'lflags' tcsetattr_flags = termios.TCSADRAIN termios.tcsetattr(stdin_fd, tcsetattr_flags, new) while True: rl, _, xl = select.select([self.ssh_channel, stdin_fd], [], []) if self.ssh_channel in rl: if self.ssh_channel.recv_ready(): new_data = self.ssh_channel.recv(4096) recv_buf += new_data if "\n" in recv_buf: lines = recv_buf.split("\n") # Last line is either not complete or empty string. # ("x\nnot compl".split("\n") => ['x', 'not compl'] or "x\n".split("\n") => ['x', '']) # so we put it back in recv_buf for next iteration recv_buf = lines.pop() for line in lines: resp = json.loads(line) if "stdout" in resp: log.white(resp["stdout"], f=sys.stdout) elif "stderr" in resp: log.white(resp["stderr"], f=sys.stderr) elif "result" in resp: if resp['error'] is not None: raise SshRpcCallError(resp['error']['message']) return resp["result"] if self.ssh_channel.recv_stderr_ready(): log.white("{}".format(self.ssh_channel.recv_stderr(4096))) if self.ssh_channel.exit_status_ready(): raise SshRpcError() if stdin_fd in rl: new_stdin_data = tty.read() self._sendall(self.stdin(new_stdin_data)) except (KeyboardInterrupt, SshRpcError): # stdin_g.kill() self.ssh_channel.shutdown(2) self.ssh_channel = None raise KeyboardInterrupt() finally: termios.tcsetattr(stdin_fd, tcsetattr_flags, old) tty.flush() # issue7208 flags &= ~os.O_NONBLOCK fcntl.fcntl(stdin_fd, fcntl.F_SETFL, flags)
def _server_update(self): try: channel = self.ssh_transport.open_session() channel.setblocking(0) # TODO: call server binary src = (repr(inspect.getsource(server_updater))+"\n").encode() channel.exec_command("env JSC_CLIENT_VERSION={version} python2 -c \"import sys;exec(eval(sys.stdin.readline()))\"".format(version=__version__)) channel.sendall(src) while True: if channel.exit_status_ready(): break rl, wl, xl = select.select([channel], [], []) for _ in rl: while channel.recv_stderr_ready(): log.white(channel.recv_stderr(4096).decode()) while channel.recv_ready(): log.white(channel.recv(4096).decode()) except paramiko.ssh_exception.SSHException: log.white("Connection lost, make sure the assembly is running, then reconnect.") os._exit(1)
def update_self(): # TODO: implement state_dir = os.path.expanduser("~/.jsc") touch_dir(state_dir) last_update_file = os.path.join(state_dir, "last_update") last_update_time = 0 if os.path.exists(last_update_file): with open(last_update_file) as f: last_update_time = int(f.read().strip()) epoch_time = int(time.time()) if (epoch_time - last_update_time) > MIN_TIME_BETWEEN_UPDATES: log.white("Checking for updates of jsc") try: response = url.urlopen(PYPI_JSON) package_json = json.loads(response.read().decode()) if dist_version.StrictVersion(package_json['info']['version']) > dist_version.StrictVersion(__version__): stop("There's a new version available, update with '# pip install -U jsc'") else: log.white("You're running the latest version of jsc") with open(last_update_file, "w") as f: f.truncate(0) f.write(str(epoch_time)) except (url.URLError, ValueError): log.white("Could not check for updates, try '# pip install -U jsc'")
def call(self, method, args): rpc_cmd = self.rpc(method, args) self._sendall(rpc_cmd) recv_buf = "" stdin_fd = os.dup(sys.stdin.fileno()) flags = fcntl.fcntl(stdin_fd, fcntl.F_GETFL, 0) flags |= os.O_NONBLOCK fcntl.fcntl(stdin_fd, fcntl.F_SETFL, flags) tty = os.fdopen(stdin_fd, "r", 0) try: # Some code stolen from getpass.py # getpass Authors: Piers Lauder (original) # Guido van Rossum (Windows support and cleanup) # Gregory P. Smith (tty support & GetPassWarning)b old = termios.tcgetattr(stdin_fd) # a copy to save new = termios.tcgetattr(stdin_fd) new[3] &= ~termios.ECHO # 3 == 'lflags' new[3] &= ~termios.ICANON # 3 == 'lflags' tcsetattr_flags = termios.TCSADRAIN termios.tcsetattr(stdin_fd, tcsetattr_flags, new) while True: rl, _, xl = select.select([self.ssh_channel, stdin_fd], [], []) if self.ssh_channel in rl: if self.ssh_channel.recv_ready(): new_data = self.ssh_channel.recv(4096) recv_buf += new_data if "\n" in recv_buf: lines = recv_buf.split("\n") # Last line is either not complete or empty string. # ("x\nnot compl".split("\n") => ['x', 'not compl'] or "x\n".split("\n") => ['x', '']) # so we put it back in recv_buf for next iteration recv_buf = lines.pop() for line in lines: resp = json.loads(line) if "stdout" in resp: log.white(resp["stdout"], f=sys.stdout) elif "stderr" in resp: log.white(resp["stderr"], f=sys.stderr) elif "result" in resp: if resp['error'] is not None: raise SshRpcCallError( resp['error']['message']) return resp["result"] if self.ssh_channel.recv_stderr_ready(): log.white("{}".format( self.ssh_channel.recv_stderr(4096))) if self.ssh_channel.exit_status_ready(): raise SshRpcError() if stdin_fd in rl: new_stdin_data = tty.read() self._sendall(self.stdin(new_stdin_data)) except (KeyboardInterrupt, SshRpcError): # stdin_g.kill() self.ssh_channel.shutdown(2) self.ssh_channel = None raise KeyboardInterrupt() finally: termios.tcsetattr(stdin_fd, tcsetattr_flags, old) tty.flush() # issue7208 flags &= ~os.O_NONBLOCK fcntl.fcntl(stdin_fd, fcntl.F_SETFL, flags)
def call(self, method, args): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) for port in range(14000, 62000): try: server_socket.bind(("127.0.0.1", port)) break except socket.error: continue server_socket.listen(5) ev = Event() input_thread = Process(target=input_reader, args=(port, ev)) input_thread.start() (input_socket, _) = server_socket.accept() input_socket.setblocking(0) rpc_cmd = self.rpc(method, args) self._sendall(rpc_cmd) recv_buf = "" try: while True: rl, _, xl = select.select([self.ssh_channel, input_socket], [], []) if self.ssh_channel in rl: if self.ssh_channel.recv_ready(): new_data = self.ssh_channel.recv(4096) recv_buf += new_data if "\n" in recv_buf: lines = recv_buf.split("\n") # Last line is either not complete or empty string. # ("x\nnot compl".split("\n") => ['x', 'not compl'] or "x\n".split("\n") => ['x', '']) # so we put it back in recv_buf for next iteration recv_buf = lines.pop() for line in lines: resp = json.loads(line) if "stdout" in resp: sys.stdout.write(resp["stdout"]) sys.stdout.flush() elif "stderr" in resp: log.white(resp["stderr"], f=sys.stderr) elif "result" in resp: if resp['error'] is not None: raise SshRpcCallError( resp['error']['message']) #print("ending",method) return resp["result"] if self.ssh_channel.recv_stderr_ready(): log.white("{}".format( self.ssh_channel.recv_stderr(4096))) if self.ssh_channel.exit_status_ready(): raise SshRpcError() if input_socket in rl: new_stdin_data = input_socket.recv(1024) self._sendall(self.stdin(new_stdin_data)) except (KeyboardInterrupt, SshRpcError): self.ssh_channel.shutdown(2) self.ssh_channel = None raise KeyboardInterrupt() finally: ev.set() input_thread.terminate() input_thread.join() del input_socket del server_socket
def fail(message): log.white(message) os._exit(1)
def stop(message): log.white(message) os._exit(0)
def default(self, line): """Called on an input line when the command prefix is not recognized. In that case we execute the line as Python code. """ log.white("unknown command: [{line}]".format(line=line))
def postloop(self): """Take care of any unfinished business. Despite the claims in the Cmd documentaion, Cmd.postloop() is not a stub. """ cmd.Cmd.postloop(self) # Clean up command completion log.white("Goodbye!")
def do_hist(self, args): """Print a list of commands that have been entered""" log.white(self._hist)