From 60427ab3d26bbe5a5ab00c4e7b550f111b4f72e5 Mon Sep 17 00:00:00 2001 From: baude Date: Fri, 22 Jun 2018 08:15:37 -0500 Subject: add podman remote client podman client that is capable of: * images * ps * rm * rmi this is only a mockup to frame out and prove python library and ssh tunnelling usage. Signed-off-by: baude Closes: #986 Approved by: rhatdan --- contrib/python/cmd/images.py | 21 ++++++ contrib/python/cmd/pman.py | 42 +++++++++++ contrib/python/cmd/ps.py | 19 +++++ contrib/python/cmd/remote_client.py | 136 ++++++++++++++++++++++++++++++++++++ contrib/python/cmd/rm.py | 22 ++++++ contrib/python/cmd/rmi.py | 25 +++++++ contrib/python/cmd/utils.py | 32 +++++++++ 7 files changed, 297 insertions(+) create mode 100644 contrib/python/cmd/images.py create mode 100644 contrib/python/cmd/pman.py create mode 100644 contrib/python/cmd/ps.py create mode 100644 contrib/python/cmd/remote_client.py create mode 100644 contrib/python/cmd/rm.py create mode 100644 contrib/python/cmd/rmi.py create mode 100644 contrib/python/cmd/utils.py (limited to 'contrib/python/cmd') diff --git a/contrib/python/cmd/images.py b/contrib/python/cmd/images.py new file mode 100644 index 000000000..3e0dff626 --- /dev/null +++ b/contrib/python/cmd/images.py @@ -0,0 +1,21 @@ +from pman import PodmanRemote +from utils import write_out, convert_size, stringTimeToHuman + +def cli(subparser): + imagesp = subparser.add_parser("images", + help=("list images")) + imagesp.add_argument("all", action="store_true", help="list all images") + imagesp.set_defaults(_class=Images, func='display_all_image_info') + + +class Images(PodmanRemote): + + def display_all_image_info(self): + col_fmt = "{0:40}{1:12}{2:14}{3:18}{4:14}" + write_out(col_fmt.format("REPOSITORY", "TAG", "IMAGE ID", "CREATED", "SIZE")) + for i in self.client.images.list(): + for r in i["repoTags"]: + rsplit = r.rindex(":") + name = r[0:rsplit-1] + tag = r[rsplit+1:] + write_out(col_fmt.format(name, tag, i["id"][:12], stringTimeToHuman(i["created"]), convert_size(i["size"]))) diff --git a/contrib/python/cmd/pman.py b/contrib/python/cmd/pman.py new file mode 100644 index 000000000..c75c3d174 --- /dev/null +++ b/contrib/python/cmd/pman.py @@ -0,0 +1,42 @@ +import podman as p + + +class PodmanRemote(object): + def __init__(self): + self.args = None + self._remote_uri= None + self._local_uri= None + self._identity_file= None + self._client = None + + def set_args(self, args, local_uri, remote_uri, identity_file): + self.args = args + self._local_uri = local_uri + self.remote_uri = remote_uri + self._identity_file = identity_file + + @property + def remote_uri(self): + return self._remote_uri + + @property + def local_uri(self): + return self._local_uri + + @property + def client(self): + if self._client is None: + self._client = p.Client(uri=self.local_uri, remote_uri=self.remote_uri, identity_file=self.identity_file) + return self._client + + @remote_uri.setter + def remote_uri(self, uri): + self._remote_uri = uri + + @local_uri.setter + def local_uri(self, uri): + self._local_uri= uri + + @property + def identity_file(self): + return self._identity_file diff --git a/contrib/python/cmd/ps.py b/contrib/python/cmd/ps.py new file mode 100644 index 000000000..85db5489e --- /dev/null +++ b/contrib/python/cmd/ps.py @@ -0,0 +1,19 @@ +from pman import PodmanRemote +from utils import write_out, convert_size, stringTimeToHuman + +def cli(subparser): + imagesp = subparser.add_parser("ps", + help=("list containers")) + imagesp.add_argument("all", action="store_true", help="list all containers") + imagesp.set_defaults(_class=Ps, func='display_all_containers') + + +class Ps(PodmanRemote): + + def display_all_containers(self): + col_fmt = "{0:15}{1:32}{2:22}{3:14}{4:12}{5:30}{6:20}" + write_out(col_fmt.format("CONTAINER ID", "IMAGE", "COMMAND", "CREATED", "STATUS", "PORTS", "NAMES")) + + for i in self.client.containers.list(): + command = " ".join(i["command"]) + write_out(col_fmt.format(i["id"][0:12], i["image"][0:30], command[0:20], stringTimeToHuman(i["createdat"]), i["status"], "", i["names"][0:20])) diff --git a/contrib/python/cmd/remote_client.py b/contrib/python/cmd/remote_client.py new file mode 100644 index 000000000..9bb5a0d9a --- /dev/null +++ b/contrib/python/cmd/remote_client.py @@ -0,0 +1,136 @@ +import os +import getpass +import argparse +import images +import ps, rm, rmi +import sys +from utils import write_err +import pytoml + +default_conf_path = "/etc/containers/podman_client.conf" + +class HelpByDefaultArgumentParser(argparse.ArgumentParser): + + def error(self, message): + write_err('%s: %s' % (self.prog, message)) + write_err("Try '%s --help' for more information." % self.prog) + sys.exit(2) + + def print_usage(self, message="too few arguments"): # pylint: disable=arguments-differ + self.prog = " ".join(sys.argv) + self.error(message) + + +def create_parser(help_text): + parser = HelpByDefaultArgumentParser(description=help_text) + parser.add_argument('-v', '--version', action='version', version="0.0", + help=("show rpodman version and exit")) + parser.add_argument('--debug', default=False, action='store_true', + help=("show debug messages")) + parser.add_argument('--run_dir', dest="run_dir", + help=("directory to place socket bindings")) + parser.add_argument('--user', dest="user", + help=("remote user")) + parser.add_argument('--host', dest="host", + help=("remote host")) + parser.add_argument('--remote_socket_path', dest="remote_socket_path", + help=("remote socket path")) + parser.add_argument('--identity_file', dest="identity_file", + help=("path to identity file")) + subparser = parser.add_subparsers(help=("commands")) + images.cli(subparser) + ps.cli(subparser) + rm.cli(subparser) + rmi.cli(subparser) + + return parser + +def load_toml(path): + # Lets load the configuration file + with open(path) as stream: + return pytoml.load(stream) + +if __name__ == '__main__': + + host = None + remote_socket_path = None + user = None + run_dir = None + + aparser = create_parser("podman remote tool") + args = aparser.parse_args() + if not os.path.exists(default_conf_path): + conf = {"default": {}} + else: + conf = load_toml("/etc/containers/podman_client.conf") + + # run_dir + if "run_dir" in os.environ: + run_dir = os.environ["run_dir"] + elif "run_dir" in conf["default"] and conf["default"]["run_dir"] is not None: + run_dir = conf["default"]["run_dir"] + else: + xdg = os.environ["XDG_RUNTIME_DIR"] + run_dir = os.path.join(xdg, "podman") + + # make the run_dir if it doesnt exist + if not os.path.exists(run_dir): + os.makedirs(run_dir) + + local_socket_path = os.path.join(run_dir, "podman.socket") + + # remote host + if "host" in os.environ: + host = os.environ["host"] + elif getattr(args, "host") is not None: + host = getattr(args, "host") + else: + host = conf["default"]["host"] if "host" in conf["default"] else None + + # remote user + if "user" in os.environ: + user = os.environ["user"] + elif getattr(args, "user") is not None: + user = getattr(args, "user") + elif "user" in conf["default"] and conf["default"]["user"] is not None: + user = conf["default"]["user"] + else: + user = getpass.getuser() + + # remote path + if "remote_socket_path" in os.environ: + remote_socket_path = os.environ["remote_socket_path"] + elif getattr(args, "remote_socket_path") is not None: + remote_socket_path = getattr(args, "remote_socket_path") + elif "remote_socket_path" in conf["default"] and conf["default"]["remote_socket_path"]: + remote_socket_path = conf["default"]["remote_socket_path"] + else: + remote_socket_path = None + + + # identity file + if "identity_file" in os.environ: + identity_file = os.environ["identity_file"] + elif getattr(args, "identity_file") is not None: + identity_file = getattr(args, "identity_file") + elif "identity_file" in conf["default"] and conf["default"]["identity_file"] is not None: + identity_file = conf["default"]["identity_file"] + else: + identity_file = None + + if None in [host, local_socket_path, user, remote_socket_path]: + print("missing input for local_socket, user, host, or remote_socket_path") + sys.exit(1) + + local_uri = "unix:{}".format(local_socket_path) + remote_uri = "ssh://{}@{}{}".format(user, host, remote_socket_path) + + _class = args._class() # pylint: disable=protected-access + _class.set_args(args, local_uri, remote_uri, identity_file) + + if "func" in args: + _func = getattr(_class, args.func) + sys.exit(_func()) + else: + aparser.print_usage() + sys.exit(1) \ No newline at end of file diff --git a/contrib/python/cmd/rm.py b/contrib/python/cmd/rm.py new file mode 100644 index 000000000..c9dfaa688 --- /dev/null +++ b/contrib/python/cmd/rm.py @@ -0,0 +1,22 @@ +from pman import PodmanRemote +from utils import write_out, convert_size, stringTimeToHuman + +def cli(subparser): + imagesp = subparser.add_parser("rm", + help=("delete one or more containers")) + imagesp.add_argument("--force", "-f", action="store_true", help="force delete", dest="force") + imagesp.add_argument("delete_targets", nargs='*', help="container images to delete") + imagesp.set_defaults(_class=Rm, func='remove_containers') + + +class Rm(PodmanRemote): + + def remove_containers(self): + delete_targets = getattr(self.args, "delete_targets") + if len(delete_targets) < 1: + raise ValueError("you must supply at least one container id or name to delete") + force = getattr(self.args, "force") + for d in delete_targets: + con = self.client.containers.get(d) + con.remove(force) + write_out(con["id"]) diff --git a/contrib/python/cmd/rmi.py b/contrib/python/cmd/rmi.py new file mode 100644 index 000000000..807c5c1e4 --- /dev/null +++ b/contrib/python/cmd/rmi.py @@ -0,0 +1,25 @@ +from pman import PodmanRemote +from utils import write_out, write_err + +def cli(subparser): + imagesp = subparser.add_parser("rmi", + help=("delete one or more images")) + imagesp.add_argument("--force", "-f", action="store_true", help="force delete", dest="force") + imagesp.add_argument("delete_targets", nargs='*', help="images to delete") + imagesp.set_defaults(_class=Rmi, func='remove_images') + + +class Rmi(PodmanRemote): + + def remove_images(self): + delete_targets = getattr(self.args, "delete_targets") + if len(delete_targets) < 1: + raise ValueError("you must supply at least one image id or name to delete") + force = getattr(self.args, "force") + for d in delete_targets: + image = self.client.images.get(d) + if image["containers"] > 0 and not force: + write_err("unable to delete {} because it has associated errors. retry with --force".format(d)) + continue + image.remove(force) + write_out(image["id"]) diff --git a/contrib/python/cmd/utils.py b/contrib/python/cmd/utils.py new file mode 100644 index 000000000..d4a14164d --- /dev/null +++ b/contrib/python/cmd/utils.py @@ -0,0 +1,32 @@ +import sys +import math +import datetime + +def write_out(output, lf="\n"): + _output(sys.stdout, output, lf) + + +def write_err(output, lf="\n"): + _output(sys.stderr, output, lf) + + +def _output(fd, output, lf): + fd.flush() + fd.write(output + str(lf)) + + +def convert_size(size): + if size > 0: + size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + i = int(math.floor(math.log(size, 1000))) + p = math.pow(1000, i) + s = round(size/p, 2) # pylint: disable=round-builtin,old-division + if s > 0: + return '%s %s' % (s, size_name[i]) + return '0B' + +def stringTimeToHuman(t): + #datetime.date(datetime.strptime("05/Feb/2016", '%d/%b/%Y')) + #2018-04-30 13:55:45.019400581 +0000 UTC + #d = datetime.date(datetime.strptime(t, "%Y-%m-%d")) + return "sometime ago" -- cgit v1.2.3-54-g00ecf