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 <bbaude@redhat.com>

Closes: #986
Approved by: rhatdan
This commit is contained in:
baude
2018-06-22 08:15:37 -05:00
committed by Atomic Bot
parent 56133f7263
commit 60427ab3d2
8 changed files with 298 additions and 1 deletions

View File

@ -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"])))

View File

@ -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

19
contrib/python/cmd/ps.py Normal file
View File

@ -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]))

View File

@ -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)

22
contrib/python/cmd/rm.py Normal file
View File

@ -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"])

25
contrib/python/cmd/rmi.py Normal file
View File

@ -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"])

View File

@ -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"

View File

@ -98,7 +98,7 @@ class Tunnel(object):
"""Create SSH tunnel from given context."""
cmd = [
'ssh',
'-nNT',
'-nNTq',
'-L',
'{}:{}'.format(self.context.local_socket,
self.context.remote_socket),