# -*- coding: utf-8 -*-
#!/usr/bin/env python
#

"""
解析docker 容器的启动参数

require: python 3

author: <NAME>
mail: <EMAIL>
date: 2021-09-24
"""

import docker
from docker.errors import ImageNotFound, APIError

from sys import argv
import re, os


def unit_converter(size: int) -> str or int:
    """存储单位转换

    byte 转换 GB、MB、KB

    :param size:
    :return:
    """
    if size <= 0:
        return 0

    if (size >> 30) > 0:
        return f"{size >> 30}GB"
    elif (size >> 20) > 0:
        return f"{size >> 20}MB"
    elif (size >> 10) > 0:
        return f"{size >> 10}KB"
    else:
        return size

def get_user_methods_by_class_(cls) ->list:
    """获取指定类的用户自定义方法

    :param cls: class
        类实例
    :return: list
       用户定义的方法名列表.
       返回的list元素为方法(fundtion)，而非str型的方法名，是可以调用的方法对象。
    """
    methds = []
    for method in dir(cls):
        if method.startswith('__'):
            continue
        if method.endswith('__'):
            continue
        if callable(getattr(cls, method)):
            methds.append(getattr(cls, method))

    return methds

def camel2connector(s: str):
    """驼峰字符转连接字符格式。

    DriverOpts -> driver-opt

    :param s:
    :return:
    """
    if len(s) <= 1:
        return s.lower()

    s_list = list(s)
    for i in range(len(s_list)):
        if i != 0 and ('A' <= s_list[i] <= 'Z'):
            s_list[i] = s_list[i].lower()
            s_list.insert(i, '-')
    ss = "".join(s_list).lower()
    if ss.endswith("s"):
        ss = ss[:-1]

    return ss

def file_mode_converter(num: int):
    """

    十进制mode 转 user:group:other mode
    0444  <-->  292
    444 --> 100 100 100(2) --> 292(10)

    :param num: 十进制数字
    :return: ser:group:other mode
        格式：0ugo
    """

    # 数字转二进制字符串
    user = (num & 0b111000000) >> 6
    group = (num & 0b111000) >> 3
    other = num & 0b111
    return f"0{user}{group}{other}"

def list_or_dict_to_ini(o, key: str):
    """list或dict对象转 initialization file 格式

    :return:
    """
    ini = ""
    try:
        if type(o) == list:
            if o :
                for i in o:
                    ini += f"{camel2connector(key)}={i},"
        elif type(o) == dict:
            for k in o:
                ini += f"{camel2connector(key)}={k}={o[k]},"
        # 去掉最后一个","
        if ini and ini.endswith(","):
            ini = ini[:-1]
    except:
        pass

    return ini

def key_in_dict(key, dic: dict, val=None) -> bool:
    """判断一个key是否存在于指定的字典中

    :param key:
    :param dic:
    :param val: object
        当key不存在时，新建的key对应的值
    :return:
    """
    try:
        if not (val is None):
            if key not in list(dic):
                dic[key] = val
        return (key in list(dic))
    except:
        return False

def remve_prefix(s: str, prefix = "/"):
    """去掉不字符串指定的前缀

    :param s:
    :param prefix:
    :return:
    """
    if s.startswith(prefix):
        return s[1:]
    return s

class MYDOCKER(object):
    def __init__(self, container=None):
        super(MYDOCKER, self).__init__()
        if not os.path.exists("/var/run/docker.sock"):
            self.help_msg()
            exit(1)
        self.client = docker.DockerClient(base_url='unix://var/run/docker.sock')
        self.api_client = docker.APIClient(base_url='unix://var/run/docker.sock')

        # container name or container id. type is str
        self.container = container
        self.inspect:dict = {}
        self.docker_run_cmd = ""
        self.options = {"kv": [], "k": []}
        self.image = None  # str
        self.args = []
        self.inspect_image:dict = {}
        self.entity_info = {'type': None, 'name': None}  # Options: container, service, stack

    def get_containers(self, all=False) -> list:
        """

        :param all: bool
            True: 所有容器，包括已经 shutdown的容器
            False: 当前在run的容器
        :return:
        """
        return self.api_client.containers(all=all)

    def check_entity_type(self):
        """判断传入的实体类型

        :return:
        """
        if not self.inspect:
            return
        if key_in_dict("Labels", self.inspect['Config']):
            if key_in_dict("com.docker.stack.namespace", self.inspect['Config']['Labels']):
                self.entity_info['type'] = "stack"
                self.entity_info['name'] = self.inspect['Config']['Labels']['com.docker.stack.namespace']
            elif key_in_dict("com.docker.swarm.service.name", self.inspect['Config']['Labels']):
                self.entity_info['type'] = "service"
                self.entity_info['name'] = self.inspect['Config']['Labels']['com.docker.swarm.service.name']
            else:
                self.entity_info['type'] = "container"

    def _print_command(self):
        """

        Usage:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

        :return: str
            运行容器的完整命令
        """
        if not self.inspect:
            return
        if self.entity_info['type'] == "stack":
            print(f"This is a docker stack: {self.entity_info['name']}.")
            print(
                "Reverse stack to a compose file reference `https://hub.docker.com/repository/docker/cucker/stack2compose`")
            print("docker run command: ")
        elif self.entity_info['type'] == "service":
            print(f"This is a docker service: {self.entity_info['name']}.")
            print(
                "Get the command for docker service create by the service reference `https://hub.docker.com/repository/docker/cucker/get_command_by_service`")
            print("docker run command: ")

        options_key = ""
        if self.options['k']:
            options_key = "-"
        #  key 型options
        for k in self.options['k']:
            options_key += f"{k}"
        options_key = options_key.strip(" ")

        # key-value 型options
        options_kv = ""
        is_pretty = len(self.options['kv']) > 2
        if is_pretty:
            for dic in self.options['kv']:
                _k = list(dic.keys())[0]
                if _k.endswith('='):
                    options_kv += f" {_k}{dic[_k]} \\\n"
                else:
                    options_kv += f" {_k} {dic[_k]} \\\n"
            if options_key:
                options = f"{options_key} \\\n {options_kv.lstrip(' ')}"
            else:
                options = f"{options_kv}".lstrip(" ")
        else:
            for dic in self.options['kv']:
                _k = list(dic.keys())[0]
                if _k.endswith('='):
                    options_kv += f"{_k}{dic[_k]} "
                else:
                    options_kv += f"{_k} {dic[_k]} "

            options = f"{options_key} {options_kv}".strip()

        command = ""
        if self.args:
            # _args = " ".join(self.args[0])
            _args = ""
            for i in self.args[0]:
                # sh -c “xxx” 命令中，-c 后的子命令需要引号包裹的情况
                if i.__contains__(' "'):
                    i = f"'{i}'"
                elif i.__contains__(" '"):
                    i = f'"{i}"'
                elif i.__contains__(" "):
                    if i.startswith('"') and i.endswith('"'):
                        pass
                    elif i.startswith("'") and i.endswith("'"):
                        pass
                    else:
                        i = f'"{i}"'
                _args += f"{i} "

            command = f"docker run {options} {self.image} {_args}"
        else:
            command = f"docker run {options} {self.image}"
        print(command)

    def _get_inspect_container(self):

        """

        :return:
        """
        try:
            self.inspect = self.api_client.inspect_container(self.container)

        except APIError as e:
            print(e)
            exit(-1)

    def _get_inspect_image(self):
        if not self.inspect:
            self._get_inspect_container()

        # image
        image = self.inspect['Config']['Image']
        try:
            self.inspect_image = self.api_client.inspect_image(image)
        except Exception as e:
            print(e)

    def _get_container_name_by_id(self, container_id):
        """通过容器ID查询容器名

        :param container_id:
        :return: str
            容器名
        """
        inspect = self.api_client.inspect_container(container_id)
        return inspect['Name'].replace("/", "")

    def _parse_inspect_container(self):
        # image
        self.image: str = self.inspect['Config']['Image'].split('@')[0]
        if self.image.startswith('sha256:'):  # 在本地没有此image的，将直接显示原来的image id. 或者有些直接指定就是image_id
            self.image = self.image.split(':')[1]

        # parse options
        obj = PARSE_OPTIONS(self.inspect, self.options, self.args, self.inspect_image)
        for m in get_user_methods_by_class_(obj):
            try:
                m()
            except:
                pass

    @staticmethod
    def help_msg():
        _MSG = """Usage:
# Command alias
echo "alias get_run_command='docker run --rm -v /var/run/docker.sock:/var/run/docker.sock cucker/get_command_4_run_container'" >> ~/.bashrc
. ~/.bashrc

# Excute command
## For all runing containers
get_run_command {allrun}

## For all containers include shutdown
get_run_command {all}

## For one or more containers
get_run_command <CONTAINER> [CONTAINER...]
"""
        print(_MSG)

    def start(self):
        self._get_inspect_container()
        self._get_inspect_image()
        self.check_entity_type()
        self._parse_inspect_container()
        self._print_command()

class PARSE_OPTIONS(object):
    """从container inspect信息中解析docker run命令的options 和 arguments

    """
    dock = MYDOCKER()

    def __init__(self, inspect: dict, options: dict, args: list, inspect_image: dict):
        self.inspect = inspect
        self.options = options
        self.args = args
        self.inspect_image = inspect_image

    # --name
    def _name(self):
        if "Name" in self.inspect:
            self.options['kv'].append(
                {"--name": self.inspect['Name'].replace("/", "")}
            )

    # --rm, Automatically remove the container when it exits。
    # --rm比较特殊，放在options['kv']的第一个。 这里把它当kv类型的option处理。
    def _rm(self):
        if self.inspect['HostConfig']['AutoRemove']:
            self.options['kv'].append(
                {"--rm": ""}
            )

    # --privileged
    def _privileged(self):
        if self.inspect['HostConfig']['Privileged']:
            self.options['kv'].append(
                {"--privileged": ""}
            )

    # --hostname
    def hostname(self):
        if not self.inspect['Id'].startswith(self.inspect['Config']['Hostname']):
            self.options['kv'].append(
                {"--hostname": self.inspect['Config']['Hostname']}
            )

    # --detach. Run container in background and print container ID
    def detach(self):
        self.options['k'].append("d")

    # --tty, -t		Allocate a pseudo-TTY
    # --interactive, -i, Keep STDIN open even if not attached
    def tty(self):
        if self.inspect['Config']['Tty']:
            self.options['k'].append("t")
        if self.inspect['Config']['OpenStdin']:
            self.options['k'].append("i")

    # --attach, -a		Attach to STDIN, STDOUT or STDERR
    def attach(self):
        if self.inspect['Config']['AttachStdin'] and self.inspect['Config']['AttachStdout'] and self.inspect['Config'][
            'AttachStderr']:
            self.options['k'].append("a")

    # --restart, "no" is default.
    def restart(self):
        _restart_policy = self.inspect['HostConfig']['RestartPolicy']
        if _restart_policy['Name'] == "on-failure":
            self.options['kv'].append(
                {"--restart=": f"on-failure:{_restart_policy['MaximumRetryCount']}"}
            )
        if _restart_policy['Name'] in ("unless-stopped", "always"):
            self.options['kv'].append(
                {"--restart=": _restart_policy['Name']}
            )

    # --mount
    def mount(self):
        if not self.inspect['Mounts']:
            return
        bs = []
        if self.inspect['HostConfig']['Binds']:
            for b in self.inspect['HostConfig']['Binds']:
                bs.append(b.split(":")[0])
        image_volumes = []
        if self.inspect['Config']['Volumes']:
            image_volumes = list(self.inspect['Config']['Volumes'].keys())
        for m in self.inspect['Mounts']:
            try:
                if m['Source'].endswith("/"):
                    source_m = m['Source'][:-1]
                else:
                    source_m = f"{m['Source']}/"

                if (m['Source'] in bs) or (source_m in bs):
                    continue
                # 在image定义的 VOLUME
                if key_in_dict("Name", m) and (len(m['Name']) == 64) and (m['Destination'] in image_volumes):
                    continue
                v = f"type={m['Type']},src={m['Source']},dst={m['Destination']}"
                if key_in_dict("Driver", m):
                    if m['Driver'] != "local":
                        v += f",volume-driver={m['Driver']}"
                if m['Mode']:
                    v += f",mode={m['Mode']}"

                self.options['kv'].append(
                    {"--mount": v}
                    )
            except Exception as e:
                print(e)
                pass

    # --volume, -v
    def volume(self):
        if not self.inspect['HostConfig']['Binds']:
            return
        for v in self.inspect['HostConfig']['Binds']:
            self.options['kv'].append(
                {'-v': v}
            )

    # --volumes-from, 挂载指定容器的数据卷
    '''
    "VolumesFrom": [
            "web_data:rw",
            "nginx-2:Z"
        ]
    '''
    def volumes_from(self):
        if self.inspect['HostConfig']['VolumesFrom']:
            for volu in self.inspect['HostConfig']['VolumesFrom']:
                self.options['kv'].append(
                    {"--volumes-from": volu}
                )

    # --publish, -p  Publish a container's port(s) to the host
    def publish(self):
        if self.inspect['HostConfig']['PortBindings']:
            _pbs = self.inspect['HostConfig']['PortBindings']
            for p in _pbs:
                for psub in _pbs[p]:
                    if psub['HostIp']:
                        self.options['kv'].append(
                            {"-p": f"{psub['HostIp']}:{psub['HostPort']}:{p}"}
                        )
                    else:
                        self.options['kv'].append(
                            {"-p": f"{psub['HostPort']}:{p}"}
                        )

    # --env, -e
    def env(self):
        # 在容器inspect['Config']['Env']存在，且在镜像inspect_image['Config']['Env']不存在的元素，则为容器运行时添加的env
        """
        "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.19.1",
                "NJS_VERSION=0.4.2",
                "PKG_RELEASE=1~buster"
            ]


        -- inspect_image
        "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.21.1",
                "NJS_VERSION=0.6.1",
                "PKG_RELEASE=1~buster"
            ]
        """
        for env in self.inspect['Config']['Env']:
            if env not in self.inspect_image['Config']['Env']:
                self.options['kv'].append(
                    {"--env": env}
                )

    # --workdir, -w
    def workdir(self):
        if self.inspect['Config']['WorkingDir']:
            self.options['kv'].append(
                {"-w": self.inspect['Config']['WorkingDir']}
            )

    # --cpu-shares, -c
    def cpu_shares(self):
        if self.inspect['HostConfig']['CpuShares'] != 0:
            self.options['kv'].append(
                {"-c": self.inspect['HostConfig']['CpuShares']}
            )

    # --cpu-period
    def cpu_period(self):
        if self.inspect['HostConfig']['CpuPeriod'] != 0:
            self.options['kv'].append(
                {"--cpu-period=": self.inspect['HostConfig']['CpuPeriod']}
            )

    # --cpu-quota
    def cpu_quota(self):
        if self.inspect['HostConfig']['CpuQuota'] != 0:
            self.options['kv'].append(
                {"--cpu-quota=": self.inspect['HostConfig']['CpuQuota']}
            )

    # --cpus
    def cpus(self):
        if self.inspect['HostConfig']['NanoCpus'] != 0:
            self.options['kv'].append(
                {"--cpus": self.inspect['HostConfig']['NanoCpus'] / 10 ** 9}
            )

    # --memory, -m
    def memory(self):
        if self.inspect['HostConfig']['Memory'] != 0:
            self.options['kv'].append(
                {"-m": unit_converter(self.inspect['HostConfig']['Memory'])}
            )

    # --blkio-weight
    def blkio_weight(self):
        if self.inspect['HostConfig']['BlkioWeight'] != 0:
            self.options['kv'].append(
                {"--blkio-weight": self.inspect['HostConfig']['BlkioWeight']}
            )

    # --device-read-bps
    def device_read_bps(self):
        if self.inspect['HostConfig']['BlkioDeviceReadBps']:
            for r in self.inspect['HostConfig']['BlkioDeviceReadBps']:
                self.options['kv'].append(
                    {"--device-read-bps": f"{r['Path']}:{unit_converter(r['Rate'])}"}
                )

    # --device-write-bps
    def device_write_bps(self):
        if self.inspect['HostConfig']['BlkioDeviceWriteBps']:
            for w in self.inspect['HostConfig']['BlkioDeviceWriteBps']:
                self.options['kv'].append(
                    {"--device-write-bps": f"{w['Path']}:{unit_converter(w['Rate'])}"}
                )

    # --device-read-iops
    def device_read_iops(self):
        if self.inspect['HostConfig']['BlkioDeviceReadIOps']:
            for rio in self.inspect['HostConfig']['BlkioDeviceReadIOps']:
                self.options['kv'].append(
                    {"--device-read-iops": f"{rio['Path']}:{rio['Rate']}"}
                )

    # --device-write-iops
    def device_write_iops(self):
        if self.inspect['HostConfig']['BlkioDeviceWriteIOps']:
            for wio in self.inspect['HostConfig']['BlkioDeviceWriteIOps']:
                self.options['kv'].append(
                    {"--device-write-iops": f"{wio['Path']}:{wio['Rate']}"}
                )

    # --net, --network
    def network(self):
        if self.inspect['HostConfig']['NetworkMode'] != 'default':
            # joined容器网络（共用容器网络）==
            '''共用容器网络 示例
            {
                "HostConfig": {
                    "NetworkMode": "container:8c9d8e9a4180f3e28c287202b4638a6ef9d26e639726b4a1140704d1ebab70b2"
                    ...
            }
                ...
            '''
            if self.inspect['HostConfig']['NetworkMode'].startswith('container:'):
                source_container_id = self.inspect['HostConfig']['NetworkMode'].split(':')[1]
                self.options['kv'].append(
                    {"--network=": "container:" + self.dock._get_container_name_by_id(source_container_id)}
                )
            else:  # 指定网络 ==
                self.options['kv'].append(
                    {"--network=": self.inspect['HostConfig']['NetworkMode']}
                )

    # --entrypoint
    def entrypoint(self):
        if not self.inspect['Config']['Entrypoint']:  # self.inspect['Config']['Entrypoint'] 为list
            return
        if self.inspect['Config']['Entrypoint'] != self.inspect_image['Config']['Entrypoint']:
            ep = " ".join(self.inspect['Config']['Entrypoint'])
            if ep.__contains__(' "'):
                v = f"'{ep}'"
            elif ep.__contains__(" '"):
                v = f'"{ep}"'
            elif ep.__contains__(" "):
                v = f'"{ep}"'
            else:
                v = ep
            self.options['kv'].append(
                {"--entrypoint": v}
            )

    # --dns
    def dns(self):
        if self.inspect['HostConfig']['Dns']:  # self.inspect['HostConfig']['Dns'] 为list
            for d in self.inspect['HostConfig']['Dns']:
                self.options['kv'].append(
                    {"--dns": d}
                )
    # --dns-option
    def dns_option(self):
        if self.inspect['HostConfig']['DnsOptions']:
            for d in self.inspect['HostConfig']['DnsOptions']:
                self.options['kv'].append(
                    {"--dns-option": d}
                )

    # --dns-search
    def dns_search(self):
        if self.inspect['HostConfig']['DnsSearch']:
            for d in self.inspect['HostConfig']['DnsSearch']:
                self.options['kv'].append(
                    {"--dns-search": d}
                )

    # --domainname
    def domainname(self):
        if self.inspect['Config']['Domainname']:
            self.options['kv'].append(
                {"--domainname": self.inspect['Config']['Domainname']}
            )

    # --user , -u
    def user(self):
        if self.inspect['Config']['User']:
            self.options['kv'].append(
                {"--user": self.inspect['Config']['User']}
            )

    # --add-host
    def add_host(self):
        if self.inspect['HostConfig']['ExtraHosts']:
            for h in self.inspect['HostConfig']['ExtraHosts']:
                self.options['kv'].append(
                    {"--add-host=": h}
                )

    # --volume-driver
    def volume_driver(self):
        if self.inspect['HostConfig']['VolumeDriver']:
            self.options['kv'].append(
                {"--volume-driver": self.inspect['HostConfig']['VolumeDriver']}
            )

    # --uts
    def uts(self):
        if self.inspect['HostConfig']['UTSMode']:
            self.options['kv'].append(
                {"--uts": self.inspect['HostConfig']['UTSMode']}
            )

    # --userns
    def userns(self):
        if self.inspect['HostConfig']['UsernsMode']:
            self.options['kv'].append(
                {"--uts": self.inspect['HostConfig']['UsernsMode']}
            )

    # --shm-size, Size of /dev/shm default is 64MB(67108864 byte).
    def shm_size(self):
        if self.inspect['HostConfig']['ShmSize'] != 67108864:
            self.options['kv'].append(
                {"--shm-size": unit_converter(self.inspect['HostConfig']['ShmSize'])}
            )

    # --link. 用于连接其它容器。是比较老旧的技术，可以把要通行的几个容器连接到同一个单独的网络 来替代
    def link(self):
        if self.inspect['HostConfig']['Links']:  # self.inspect['HostConfig']['Links'] 为null或[]
            for i in self.inspect['HostConfig']['Links']:
                self.options['kv'].append(
                    {"--add-host=": i}
                )

    # --ipc
    def ipc(self):
        if self.inspect['HostConfig']['IpcMode'] and (self.inspect['HostConfig']['IpcMode'] != "private"):
            self.options['kv'].append(
                {"--ipc": self.inspect['HostConfig']['IpcMode']}
            )
    # --log-driver
    # --log-opt
    def log_config(self):
        logconfig: dict = self.inspect['HostConfig']['LogConfig']
        ## --log-driver
        if key_in_dict("Type", logconfig):
            if logconfig['Type'] != "json-file":
                self.options['kv'].append(
                    {'--log-driver': logconfig['Type']}
                )
        # --log-opt
        if key_in_dict("Config", logconfig):
            for k in logconfig['Config']:
                self.options['kv'].append(
                    {'--log-opt': f"{k}={logconfig['Config'][k]}"}
                )


    # --label , -l
    def label(self):
        labels: dict = self.inspect['Config']['Labels']
        for k in labels:
            if not key_in_dict(k, self.inspect_image['Config']['Labels']):
                if labels[k]:
                    self.options['kv'].append(
                        {'--label': f"{k}={labels[k]}"}
                    )
                else:
                    self.options['kv'].append(
                        {'--label': f'{k}=""'}
                    )

    # --init
    def init(self):
        if self.inspect['HostConfig']['Init']:  # inspect['HostConfig']['Init'] 为 bool值
            self.options['kv'].append(
                {'--init': ''}
            )


    # --stop-signal
    def stop_signal(self):
        if key_in_dict("StopSignal", self.inspect['Config']):
            self.options['kv'].append(
                {'--stop-signal': self.inspect['Config']['StopSignal']}
            )

    # --health-cmd
    # --health-interval
    # --health-retries
    # --health-start-period
    # --health-timeout
    # --no-healthcheck
    def health_check(self):
        hc: dict = self.inspect['Config']['Healthcheck']
        # --health-cmd
        try:
            if hc['Test'][0] == "CMD-SHELL":
                self.options['kv'].append(
                    {'--health-cmd': f'"{hc["Test"][1]}"'}
                )
            # --no-healthcheck
            elif hc['Test'][0] == "NONE":
                self.options['kv'].append(
                    {'--no-healthcheck': ""}
                )
        except:
            pass

        # --health-interval
        try:
            if hc['Interval']:
                self.options['kv'].append(
                    {'--health-interval': f"{int(hc['Interval'] / 10 ** 9)}s"}
                )
        except:
            pass

        # --health-retries
        try:
            if hc['Retries']:
                self.options['kv'].append(
                    {'--health-retries': hc['Retries']}
                )
        except:
            pass

        # --health-start-period
        try:
            if hc['StartPeriod']:
                self.options['kv'].append(
                    {'--health-start-period': f"{int(hc['StartPeriod'] / 10 ** 9)}s"}
                )
        except:
            pass

        # --health-timeout
        try:
            if hc['Timeout']:
                self.options['kv'].append(
                    {'--health-timeout': f"{int(hc['Timeout'] / 10 ** 9)}s"}
                )
        except:
            pass

    # --isolation
    def isolation(self):
        if self.inspect['HostConfig']['Isolation'] and (self.inspect['HostConfig']['Isolation'] != "default"):
            self.options['kv'].append(
                {'--isolation': self.inspect['HostConfig']['Isolation']}
            )

    # --device
    def device(self):
        devices: list = self.inspect['HostConfig']['Devices']
        for d in devices:
            if d['CgroupPermissions'] == "rwm":
                v = f"{d['PathOnHost']}:{d['PathInContainer']}"
            else:
                v = f"{d['PathOnHost']}:{d['PathInContainer']}:{d['CgroupPermissions']}"
            self.options['kv'].append(
                {'--device=': v}
            )

    # --sysctl
    def sysctl(self):
        sysctls: dict = self.inspect['HostConfig']['Sysctls']
        for k in sysctls:
            self.options['kv'].append(
                {'--sysctl': f"{k}={sysctls[k]}"}
            )

    # --pid
    def pid(self):
        if self.inspect['HostConfig']['PidMode']:
            self.options['kv'].append(
                {'--pid': self.inspect['HostConfig']['PidMode']}
            )
    # --cap-add
    def cap_add(self):
        if self.inspect['HostConfig']['CapAdd']:
            for c in self.inspect['HostConfig']['CapAdd']:
                self.options['kv'].append(
                    {'--cap-add': c}
                )


    # --cap-drop
    def cap_drop(self):
        if self.inspect['HostConfig']['CapDrop']:
            for c in self.inspect['HostConfig']['CapDrop']:
                self.options['kv'].append(
                    {'--cap-drop': c}
                )

    # --ulimit
    def ulimit(self):
        ulimits: list = self.inspect['HostConfig']['Ulimits']
        for u in ulimits:
            if u['Hard'] != u['Soft']:
                v = f"{u['Soft']}:{u['Hard']}"
            else:
                v = u['Soft']
            self.options['kv'].append(
                {'--ulimit': f"{u['Name']}={v}"}
            )

    # --cgroupns, Cgroup namespace to use (host|private|'')
    def cgroupns(self):
        if not self.inspect['HostConfig']['CgroupnsMode']:
            return
        if self.inspect['HostConfig']['CgroupnsMode'] != "host":
            self.options['kv'].append(
                {'--cgroupns': self.inspect['HostConfig']['CgroupnsMode']}
            )

    # --cgroup-parent
    def cgroup_parent(self):
        v = self.inspect['HostConfig']['CgroupParent']
        if v:
            self.options['kv'].append(
                {'--cgroup-parent': v}
            )

    # --tmpfs
    def tmpfs(self):
        if not key_in_dict("Tmpfs", self.inspect['HostConfig']):
            return
        ts: dict = self.inspect['HostConfig']['Tmpfs']
        for k in ts:
            self.options['kv'].append(
                {'--tmpfs': f"{k}:{ts[k]}"}
            )

    # --cidfile
    def cidfile(self):
        v = self.inspect['HostConfig']['ContainerIDFile']
        if v:
            self.options['kv'].append(
                {'--cidfile': v}
            )

    # --cpu-rt-period, Limit CPU real-time period in microseconds
    def cpu_rt_period(self):
        v: int = self.inspect['HostConfig']['CpuRealtimePeriod']
        if v != 0:
            self.options['kv'].append(
                {'--cpu-rt-period': v}
            )

    # --cpu-rt-runtime, Limit CPU real-time runtime in microseconds
    def cpu_rt_runtime(self):
        v: int = self.inspect['HostConfig']['CpuRealtimeRuntime']
        if v != 0:
            self.options['kv'].append(
                {'--cpu-rt-runtime': v}
            )

    # command and args
    def arguments(self):
        # 如果 self.inspect['Config']['Cmd'] 与 self.inspect_image['Config']['Cmd'] 不相同，说明docker run传参数了。
        if self.inspect['Config']['Cmd'] and (self.inspect['Config']['Cmd'] != self.inspect_image['Config']['Cmd']):
            self.args.append(self.inspect['Config']['Cmd'])

def main():
    if len(argv) < 2 or argv[1] == "--help":
        MYDOCKER.help_msg()
        exit(1)

    # 查看所有container的docker run命令
    elif argv[1] in ("{all}", "{allrun}"):
        # {all}: 包括shutdown的所有容器
        # {allrun}: 当前状态为run的所有容器
        is_all = False
        if argv[1] == "{all}":
            is_all = True

        for container in MYDOCKER().get_containers(is_all):
            print(f"=== container: {remve_prefix(container['Names'][0])} ===")
            try:
                MYDOCKER(container['Id']).start()
            except:
                pass
            print("\n")
    elif len(argv) > 2:
        for s in argv[1:]:
            print(f"=== container: {s} ===")
            try:
                MYDOCKER(s).start()
            except:
                pass
            print("\n")
    else:
        MYDOCKER(argv[1]).start()

if __name__ == '__main__':
    main()

# -*- coding: utf-8 -*-
#!/usr/bin/env python
#

"""
解析docker 容器的启动参数

require: python 3

author: <NAME>
mail: <EMAIL>
date: 2021-09-24
"""

import docker
from docker.errors import ImageNotFound, APIError

from sys import argv
import re, os


def unit_converter(size: int) -> str or int:
    """存储单位转换

    byte 转换 GB、MB、KB

    :param size:
    :return:
    """
    if size <= 0:
        return 0

    if (size >> 30) > 0:
        return f"{size >> 30}GB"
    elif (size >> 20) > 0:
        return f"{size >> 20}MB"
    elif (size >> 10) > 0:
        return f"{size >> 10}KB"
    else:
        return size

def get_user_methods_by_class_(cls) ->list:
    """获取指定类的用户自定义方法

    :param cls: class
        类实例
    :return: list
       用户定义的方法名列表.
       返回的list元素为方法(fundtion)，而非str型的方法名，是可以调用的方法对象。
    """
    methds = []
    for method in dir(cls):
        if method.startswith('__'):
            continue
        if method.endswith('__'):
            continue
        if callable(getattr(cls, method)):
            methds.append(getattr(cls, method))

    return methds

def camel2connector(s: str):
    """驼峰字符转连接字符格式。

    DriverOpts -> driver-opt

    :param s:
    :return:
    """
    if len(s) <= 1:
        return s.lower()

    s_list = list(s)
    for i in range(len(s_list)):
        if i != 0 and ('A' <= s_list[i] <= 'Z'):
            s_list[i] = s_list[i].lower()
            s_list.insert(i, '-')
    ss = "".join(s_list).lower()
    if ss.endswith("s"):
        ss = ss[:-1]

    return ss

def file_mode_converter(num: int):
    """

    十进制mode 转 user:group:other mode
    0444  <-->  292
    444 --> 100 100 100(2) --> 292(10)

    :param num: 十进制数字
    :return: ser:group:other mode
        格式：0ugo
    """

    # 数字转二进制字符串
    user = (num & 0b111000000) >> 6
    group = (num & 0b111000) >> 3
    other = num & 0b111
    return f"0{user}{group}{other}"

def list_or_dict_to_ini(o, key: str):
    """list或dict对象转 initialization file 格式

    :return:
    """
    ini = ""
    try:
        if type(o) == list:
            if o :
                for i in o:
                    ini += f"{camel2connector(key)}={i},"
        elif type(o) == dict:
            for k in o:
                ini += f"{camel2connector(key)}={k}={o[k]},"
        # 去掉最后一个","
        if ini and ini.endswith(","):
            ini = ini[:-1]
    except:
        pass

    return ini

def key_in_dict(key, dic: dict, val=None) -> bool:
    """判断一个key是否存在于指定的字典中

    :param key:
    :param dic:
    :param val: object
        当key不存在时，新建的key对应的值
    :return:
    """
    try:
        if not (val is None):
            if key not in list(dic):
                dic[key] = val
        return (key in list(dic))
    except:
        return False

def remve_prefix(s: str, prefix = "/"):
    """去掉不字符串指定的前缀

    :param s:
    :param prefix:
    :return:
    """
    if s.startswith(prefix):
        return s[1:]
    return s

class MYDOCKER(object):
    def __init__(self, container=None):
        super(MYDOCKER, self).__init__()
        if not os.path.exists("/var/run/docker.sock"):
            self.help_msg()
            exit(1)
        self.client = docker.DockerClient(base_url='unix://var/run/docker.sock')
        self.api_client = docker.APIClient(base_url='unix://var/run/docker.sock')

        # container name or container id. type is str
        self.container = container
        self.inspect:dict = {}
        self.docker_run_cmd = ""
        self.options = {"kv": [], "k": []}
        self.image = None  # str
        self.args = []
        self.inspect_image:dict = {}
        self.entity_info = {'type': None, 'name': None}  # Options: container, service, stack

    def get_containers(self, all=False) -> list:
        """

        :param all: bool
            True: 所有容器，包括已经 shutdown的容器
            False: 当前在run的容器
        :return:
        """
        return self.api_client.containers(all=all)

    def check_entity_type(self):
        """判断传入的实体类型

        :return:
        """
        if not self.inspect:
            return
        if key_in_dict("Labels", self.inspect['Config']):
            if key_in_dict("com.docker.stack.namespace", self.inspect['Config']['Labels']):
                self.entity_info['type'] = "stack"
                self.entity_info['name'] = self.inspect['Config']['Labels']['com.docker.stack.namespace']
            elif key_in_dict("com.docker.swarm.service.name", self.inspect['Config']['Labels']):
                self.entity_info['type'] = "service"
                self.entity_info['name'] = self.inspect['Config']['Labels']['com.docker.swarm.service.name']
            else:
                self.entity_info['type'] = "container"

    def _print_command(self):
        """

        Usage:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

        :return: str
            运行容器的完整命令
        """
        if not self.inspect:
            return
        if self.entity_info['type'] == "stack":
            print(f"This is a docker stack: {self.entity_info['name']}.")
            print(
                "Reverse stack to a compose file reference `https://hub.docker.com/repository/docker/cucker/stack2compose`")
            print("docker run command: ")
        elif self.entity_info['type'] == "service":
            print(f"This is a docker service: {self.entity_info['name']}.")
            print(
                "Get the command for docker service create by the service reference `https://hub.docker.com/repository/docker/cucker/get_command_by_service`")
            print("docker run command: ")

        options_key = ""
        if self.options['k']:
            options_key = "-"
        #  key 型options
        for k in self.options['k']:
            options_key += f"{k}"
        options_key = options_key.strip(" ")

        # key-value 型options
        options_kv = ""
        is_pretty = len(self.options['kv']) > 2
        if is_pretty:
            for dic in self.options['kv']:
                _k = list(dic.keys())[0]
                if _k.endswith('='):
                    options_kv += f" {_k}{dic[_k]} \\\n"
                else:
                    options_kv += f" {_k} {dic[_k]} \\\n"
            if options_key:
                options = f"{options_key} \\\n {options_kv.lstrip(' ')}"
            else:
                options = f"{options_kv}".lstrip(" ")
        else:
            for dic in self.options['kv']:
                _k = list(dic.keys())[0]
                if _k.endswith('='):
                    options_kv += f"{_k}{dic[_k]} "
                else:
                    options_kv += f"{_k} {dic[_k]} "

            options = f"{options_key} {options_kv}".strip()

        command = ""
        if self.args:
            # _args = " ".join(self.args[0])
            _args = ""
            for i in self.args[0]:
                # sh -c “xxx” 命令中，-c 后的子命令需要引号包裹的情况
                if i.__contains__(' "'):
                    i = f"'{i}'"
                elif i.__contains__(" '"):
                    i = f'"{i}"'
                elif i.__contains__(" "):
                    if i.startswith('"') and i.endswith('"'):
                        pass
                    elif i.startswith("'") and i.endswith("'"):
                        pass
                    else:
                        i = f'"{i}"'
                _args += f"{i} "

            command = f"docker run {options} {self.image} {_args}"
        else:
            command = f"docker run {options} {self.image}"
        print(command)

    def _get_inspect_container(self):

        """

        :return:
        """
        try:
            self.inspect = self.api_client.inspect_container(self.container)

        except APIError as e:
            print(e)
            exit(-1)

    def _get_inspect_image(self):
        if not self.inspect:
            self._get_inspect_container()

        # image
        image = self.inspect['Config']['Image']
        try:
            self.inspect_image = self.api_client.inspect_image(image)
        except Exception as e:
            print(e)

    def _get_container_name_by_id(self, container_id):
        """通过容器ID查询容器名

        :param container_id:
        :return: str
            容器名
        """
        inspect = self.api_client.inspect_container(container_id)
        return inspect['Name'].replace("/", "")

    def _parse_inspect_container(self):
        # image
        self.image: str = self.inspect['Config']['Image'].split('@')[0]
        if self.image.startswith('sha256:'):  # 在本地没有此image的，将直接显示原来的image id. 或者有些直接指定就是image_id
            self.image = self.image.split(':')[1]

        # parse options
        obj = PARSE_OPTIONS(self.inspect, self.options, self.args, self.inspect_image)
        for m in get_user_methods_by_class_(obj):
            try:
                m()
            except:
                pass

    @staticmethod
    def help_msg():
        _MSG = """Usage:
# Command alias
echo "alias get_run_command='docker run --rm -v /var/run/docker.sock:/var/run/docker.sock cucker/get_command_4_run_container'" >> ~/.bashrc
. ~/.bashrc

# Excute command
## For all runing containers
get_run_command {allrun}

## For all containers include shutdown
get_run_command {all}

## For one or more containers
get_run_command <CONTAINER> [CONTAINER...]
"""
        print(_MSG)

    def start(self):
        self._get_inspect_container()
        self._get_inspect_image()
        self.check_entity_type()
        self._parse_inspect_container()
        self._print_command()

class PARSE_OPTIONS(object):
    """从container inspect信息中解析docker run命令的options 和 arguments

    """
    dock = MYDOCKER()

    def __init__(self, inspect: dict, options: dict, args: list, inspect_image: dict):
        self.inspect = inspect
        self.options = options
        self.args = args
        self.inspect_image = inspect_image

    # --name
    def _name(self):
        if "Name" in self.inspect:
            self.options['kv'].append(
                {"--name": self.inspect['Name'].replace("/", "")}
            )

    # --rm, Automatically remove the container when it exits。
    # --rm比较特殊，放在options['kv']的第一个。 这里把它当kv类型的option处理。
    def _rm(self):
        if self.inspect['HostConfig']['AutoRemove']:
            self.options['kv'].append(
                {"--rm": ""}
            )

    # --privileged
    def _privileged(self):
        if self.inspect['HostConfig']['Privileged']:
            self.options['kv'].append(
                {"--privileged": ""}
            )

    # --hostname
    def hostname(self):
        if not self.inspect['Id'].startswith(self.inspect['Config']['Hostname']):
            self.options['kv'].append(
                {"--hostname": self.inspect['Config']['Hostname']}
            )

    # --detach. Run container in background and print container ID
    def detach(self):
        self.options['k'].append("d")

    # --tty, -t		Allocate a pseudo-TTY
    # --interactive, -i, Keep STDIN open even if not attached
    def tty(self):
        if self.inspect['Config']['Tty']:
            self.options['k'].append("t")
        if self.inspect['Config']['OpenStdin']:
            self.options['k'].append("i")

    # --attach, -a		Attach to STDIN, STDOUT or STDERR
    def attach(self):
        if self.inspect['Config']['AttachStdin'] and self.inspect['Config']['AttachStdout'] and self.inspect['Config'][
            'AttachStderr']:
            self.options['k'].append("a")

    # --restart, "no" is default.
    def restart(self):
        _restart_policy = self.inspect['HostConfig']['RestartPolicy']
        if _restart_policy['Name'] == "on-failure":
            self.options['kv'].append(
                {"--restart=": f"on-failure:{_restart_policy['MaximumRetryCount']}"}
            )
        if _restart_policy['Name'] in ("unless-stopped", "always"):
            self.options['kv'].append(
                {"--restart=": _restart_policy['Name']}
            )

    # --mount
    def mount(self):
        if not self.inspect['Mounts']:
            return
        bs = []
        if self.inspect['HostConfig']['Binds']:
            for b in self.inspect['HostConfig']['Binds']:
                bs.append(b.split(":")[0])
        image_volumes = []
        if self.inspect['Config']['Volumes']:
            image_volumes = list(self.inspect['Config']['Volumes'].keys())
        for m in self.inspect['Mounts']:
            try:
                if m['Source'].endswith("/"):
                    source_m = m['Source'][:-1]
                else:
                    source_m = f"{m['Source']}/"

                if (m['Source'] in bs) or (source_m in bs):
                    continue
                # 在image定义的 VOLUME
                if key_in_dict("Name", m) and (len(m['Name']) == 64) and (m['Destination'] in image_volumes):
                    continue
                v = f"type={m['Type']},src={m['Source']},dst={m['Destination']}"
                if key_in_dict("Driver", m):
                    if m['Driver'] != "local":
                        v += f",volume-driver={m['Driver']}"
                if m['Mode']:
                    v += f",mode={m['Mode']}"

                self.options['kv'].append(
                    {"--mount": v}
                    )
            except Exception as e:
                print(e)
                pass

    # --volume, -v
    def volume(self):
        if not self.inspect['HostConfig']['Binds']:
            return
        for v in self.inspect['HostConfig']['Binds']:
            self.options['kv'].append(
                {'-v': v}
            )

    # --volumes-from, 挂载指定容器的数据卷
    '''
    "VolumesFrom": [
            "web_data:rw",
            "nginx-2:Z"
        ]
    '''
    def volumes_from(self):
        if self.inspect['HostConfig']['VolumesFrom']:
            for volu in self.inspect['HostConfig']['VolumesFrom']:
                self.options['kv'].append(
                    {"--volumes-from": volu}
                )

    # --publish, -p  Publish a container's port(s) to the host
    def publish(self):
        if self.inspect['HostConfig']['PortBindings']:
            _pbs = self.inspect['HostConfig']['PortBindings']
            for p in _pbs:
                for psub in _pbs[p]:
                    if psub['HostIp']:
                        self.options['kv'].append(
                            {"-p": f"{psub['HostIp']}:{psub['HostPort']}:{p}"}
                        )
                    else:
                        self.options['kv'].append(
                            {"-p": f"{psub['HostPort']}:{p}"}
                        )

    # --env, -e
    def env(self):
        # 在容器inspect['Config']['Env']存在，且在镜像inspect_image['Config']['Env']不存在的元素，则为容器运行时添加的env
        """
        "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.19.1",
                "NJS_VERSION=0.4.2",
                "PKG_RELEASE=1~buster"
            ]


        -- inspect_image
        "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.21.1",
                "NJS_VERSION=0.6.1",
                "PKG_RELEASE=1~buster"
            ]
        """
        for env in self.inspect['Config']['Env']:
            if env not in self.inspect_image['Config']['Env']:
                self.options['kv'].append(
                    {"--env": env}
                )

    # --workdir, -w
    def workdir(self):
        if self.inspect['Config']['WorkingDir']:
            self.options['kv'].append(
                {"-w": self.inspect['Config']['WorkingDir']}
            )

    # --cpu-shares, -c
    def cpu_shares(self):
        if self.inspect['HostConfig']['CpuShares'] != 0:
            self.options['kv'].append(
                {"-c": self.inspect['HostConfig']['CpuShares']}
            )

    # --cpu-period
    def cpu_period(self):
        if self.inspect['HostConfig']['CpuPeriod'] != 0:
            self.options['kv'].append(
                {"--cpu-period=": self.inspect['HostConfig']['CpuPeriod']}
            )

    # --cpu-quota
    def cpu_quota(self):
        if self.inspect['HostConfig']['CpuQuota'] != 0:
            self.options['kv'].append(
                {"--cpu-quota=": self.inspect['HostConfig']['CpuQuota']}
            )

    # --cpus
    def cpus(self):
        if self.inspect['HostConfig']['NanoCpus'] != 0:
            self.options['kv'].append(
                {"--cpus": self.inspect['HostConfig']['NanoCpus'] / 10 ** 9}
            )

    # --memory, -m
    def memory(self):
        if self.inspect['HostConfig']['Memory'] != 0:
            self.options['kv'].append(
                {"-m": unit_converter(self.inspect['HostConfig']['Memory'])}
            )

    # --blkio-weight
    def blkio_weight(self):
        if self.inspect['HostConfig']['BlkioWeight'] != 0:
            self.options['kv'].append(
                {"--blkio-weight": self.inspect['HostConfig']['BlkioWeight']}
            )

    # --device-read-bps
    def device_read_bps(self):
        if self.inspect['HostConfig']['BlkioDeviceReadBps']:
            for r in self.inspect['HostConfig']['BlkioDeviceReadBps']:
                self.options['kv'].append(
                    {"--device-read-bps": f"{r['Path']}:{unit_converter(r['Rate'])}"}
                )

    # --device-write-bps
    def device_write_bps(self):
        if self.inspect['HostConfig']['BlkioDeviceWriteBps']:
            for w in self.inspect['HostConfig']['BlkioDeviceWriteBps']:
                self.options['kv'].append(
                    {"--device-write-bps": f"{w['Path']}:{unit_converter(w['Rate'])}"}
                )

    # --device-read-iops
    def device_read_iops(self):
        if self.inspect['HostConfig']['BlkioDeviceReadIOps']:
            for rio in self.inspect['HostConfig']['BlkioDeviceReadIOps']:
                self.options['kv'].append(
                    {"--device-read-iops": f"{rio['Path']}:{rio['Rate']}"}
                )

    # --device-write-iops
    def device_write_iops(self):
        if self.inspect['HostConfig']['BlkioDeviceWriteIOps']:
            for wio in self.inspect['HostConfig']['BlkioDeviceWriteIOps']:
                self.options['kv'].append(
                    {"--device-write-iops": f"{wio['Path']}:{wio['Rate']}"}
                )

    # --net, --network
    def network(self):
        if self.inspect['HostConfig']['NetworkMode'] != 'default':
            # joined容器网络（共用容器网络）==
            '''共用容器网络 示例
            {
                "HostConfig": {
                    "NetworkMode": "container:8c9d8e9a4180f3e28c287202b4638a6ef9d26e639726b4a1140704d1ebab70b2"
                    ...
            }
                ...
            '''
            if self.inspect['HostConfig']['NetworkMode'].startswith('container:'):
                source_container_id = self.inspect['HostConfig']['NetworkMode'].split(':')[1]
                self.options['kv'].append(
                    {"--network=": "container:" + self.dock._get_container_name_by_id(source_container_id)}
                )
            else:  # 指定网络 ==
                self.options['kv'].append(
                    {"--network=": self.inspect['HostConfig']['NetworkMode']}
                )

    # --entrypoint
    def entrypoint(self):
        if not self.inspect['Config']['Entrypoint']:  # self.inspect['Config']['Entrypoint'] 为list
            return
        if self.inspect['Config']['Entrypoint'] != self.inspect_image['Config']['Entrypoint']:
            ep = " ".join(self.inspect['Config']['Entrypoint'])
            if ep.__contains__(' "'):
                v = f"'{ep}'"
            elif ep.__contains__(" '"):
                v = f'"{ep}"'
            elif ep.__contains__(" "):
                v = f'"{ep}"'
            else:
                v = ep
            self.options['kv'].append(
                {"--entrypoint": v}
            )

    # --dns
    def dns(self):
        if self.inspect['HostConfig']['Dns']:  # self.inspect['HostConfig']['Dns'] 为list
            for d in self.inspect['HostConfig']['Dns']:
                self.options['kv'].append(
                    {"--dns": d}
                )
    # --dns-option
    def dns_option(self):
        if self.inspect['HostConfig']['DnsOptions']:
            for d in self.inspect['HostConfig']['DnsOptions']:
                self.options['kv'].append(
                    {"--dns-option": d}
                )

    # --dns-search
    def dns_search(self):
        if self.inspect['HostConfig']['DnsSearch']:
            for d in self.inspect['HostConfig']['DnsSearch']:
                self.options['kv'].append(
                    {"--dns-search": d}
                )

    # --domainname
    def domainname(self):
        if self.inspect['Config']['Domainname']:
            self.options['kv'].append(
                {"--domainname": self.inspect['Config']['Domainname']}
            )

    # --user , -u
    def user(self):
        if self.inspect['Config']['User']:
            self.options['kv'].append(
                {"--user": self.inspect['Config']['User']}
            )

    # --add-host
    def add_host(self):
        if self.inspect['HostConfig']['ExtraHosts']:
            for h in self.inspect['HostConfig']['ExtraHosts']:
                self.options['kv'].append(
                    {"--add-host=": h}
                )

    # --volume-driver
    def volume_driver(self):
        if self.inspect['HostConfig']['VolumeDriver']:
            self.options['kv'].append(
                {"--volume-driver": self.inspect['HostConfig']['VolumeDriver']}
            )

    # --uts
    def uts(self):
        if self.inspect['HostConfig']['UTSMode']:
            self.options['kv'].append(
                {"--uts": self.inspect['HostConfig']['UTSMode']}
            )

    # --userns
    def userns(self):
        if self.inspect['HostConfig']['UsernsMode']:
            self.options['kv'].append(
                {"--uts": self.inspect['HostConfig']['UsernsMode']}
            )

    # --shm-size, Size of /dev/shm default is 64MB(67108864 byte).
    def shm_size(self):
        if self.inspect['HostConfig']['ShmSize'] != 67108864:
            self.options['kv'].append(
                {"--shm-size": unit_converter(self.inspect['HostConfig']['ShmSize'])}
            )

    # --link. 用于连接其它容器。是比较老旧的技术，可以把要通行的几个容器连接到同一个单独的网络 来替代
    def link(self):
        if self.inspect['HostConfig']['Links']:  # self.inspect['HostConfig']['Links'] 为null或[]
            for i in self.inspect['HostConfig']['Links']:
                self.options['kv'].append(
                    {"--add-host=": i}
                )

    # --ipc
    def ipc(self):
        if self.inspect['HostConfig']['IpcMode'] and (self.inspect['HostConfig']['IpcMode'] != "private"):
            self.options['kv'].append(
                {"--ipc": self.inspect['HostConfig']['IpcMode']}
            )
    # --log-driver
    # --log-opt
    def log_config(self):
        logconfig: dict = self.inspect['HostConfig']['LogConfig']
        ## --log-driver
        if key_in_dict("Type", logconfig):
            if logconfig['Type'] != "json-file":
                self.options['kv'].append(
                    {'--log-driver': logconfig['Type']}
                )
        # --log-opt
        if key_in_dict("Config", logconfig):
            for k in logconfig['Config']:
                self.options['kv'].append(
                    {'--log-opt': f"{k}={logconfig['Config'][k]}"}
                )


    # --label , -l
    def label(self):
        labels: dict = self.inspect['Config']['Labels']
        for k in labels:
            if not key_in_dict(k, self.inspect_image['Config']['Labels']):
                if labels[k]:
                    self.options['kv'].append(
                        {'--label': f"{k}={labels[k]}"}
                    )
                else:
                    self.options['kv'].append(
                        {'--label': f'{k}=""'}
                    )

    # --init
    def init(self):
        if self.inspect['HostConfig']['Init']:  # inspect['HostConfig']['Init'] 为 bool值
            self.options['kv'].append(
                {'--init': ''}
            )


    # --stop-signal
    def stop_signal(self):
        if key_in_dict("StopSignal", self.inspect['Config']):
            self.options['kv'].append(
                {'--stop-signal': self.inspect['Config']['StopSignal']}
            )

    # --health-cmd
    # --health-interval
    # --health-retries
    # --health-start-period
    # --health-timeout
    # --no-healthcheck
    def health_check(self):
        hc: dict = self.inspect['Config']['Healthcheck']
        # --health-cmd
        try:
            if hc['Test'][0] == "CMD-SHELL":
                self.options['kv'].append(
                    {'--health-cmd': f'"{hc["Test"][1]}"'}
                )
            # --no-healthcheck
            elif hc['Test'][0] == "NONE":
                self.options['kv'].append(
                    {'--no-healthcheck': ""}
                )
        except:
            pass

        # --health-interval
        try:
            if hc['Interval']:
                self.options['kv'].append(
                    {'--health-interval': f"{int(hc['Interval'] / 10 ** 9)}s"}
                )
        except:
            pass

        # --health-retries
        try:
            if hc['Retries']:
                self.options['kv'].append(
                    {'--health-retries': hc['Retries']}
                )
        except:
            pass

        # --health-start-period
        try:
            if hc['StartPeriod']:
                self.options['kv'].append(
                    {'--health-start-period': f"{int(hc['StartPeriod'] / 10 ** 9)}s"}
                )
        except:
            pass

        # --health-timeout
        try:
            if hc['Timeout']:
                self.options['kv'].append(
                    {'--health-timeout': f"{int(hc['Timeout'] / 10 ** 9)}s"}
                )
        except:
            pass

    # --isolation
    def isolation(self):
        if self.inspect['HostConfig']['Isolation'] and (self.inspect['HostConfig']['Isolation'] != "default"):
            self.options['kv'].append(
                {'--isolation': self.inspect['HostConfig']['Isolation']}
            )

    # --device
    def device(self):
        devices: list = self.inspect['HostConfig']['Devices']
        for d in devices:
            if d['CgroupPermissions'] == "rwm":
                v = f"{d['PathOnHost']}:{d['PathInContainer']}"
            else:
                v = f"{d['PathOnHost']}:{d['PathInContainer']}:{d['CgroupPermissions']}"
            self.options['kv'].append(
                {'--device=': v}
            )

    # --sysctl
    def sysctl(self):
        sysctls: dict = self.inspect['HostConfig']['Sysctls']
        for k in sysctls:
            self.options['kv'].append(
                {'--sysctl': f"{k}={sysctls[k]}"}
            )

    # --pid
    def pid(self):
        if self.inspect['HostConfig']['PidMode']:
            self.options['kv'].append(
                {'--pid': self.inspect['HostConfig']['PidMode']}
            )
    # --cap-add
    def cap_add(self):
        if self.inspect['HostConfig']['CapAdd']:
            for c in self.inspect['HostConfig']['CapAdd']:
                self.options['kv'].append(
                    {'--cap-add': c}
                )


    # --cap-drop
    def cap_drop(self):
        if self.inspect['HostConfig']['CapDrop']:
            for c in self.inspect['HostConfig']['CapDrop']:
                self.options['kv'].append(
                    {'--cap-drop': c}
                )

    # --ulimit
    def ulimit(self):
        ulimits: list = self.inspect['HostConfig']['Ulimits']
        for u in ulimits:
            if u['Hard'] != u['Soft']:
                v = f"{u['Soft']}:{u['Hard']}"
            else:
                v = u['Soft']
            self.options['kv'].append(
                {'--ulimit': f"{u['Name']}={v}"}
            )

    # --cgroupns, Cgroup namespace to use (host|private|'')
    def cgroupns(self):
        if not self.inspect['HostConfig']['CgroupnsMode']:
            return
        if self.inspect['HostConfig']['CgroupnsMode'] != "host":
            self.options['kv'].append(
                {'--cgroupns': self.inspect['HostConfig']['CgroupnsMode']}
            )

    # --cgroup-parent
    def cgroup_parent(self):
        v = self.inspect['HostConfig']['CgroupParent']
        if v:
            self.options['kv'].append(
                {'--cgroup-parent': v}
            )

    # --tmpfs
    def tmpfs(self):
        if not key_in_dict("Tmpfs", self.inspect['HostConfig']):
            return
        ts: dict = self.inspect['HostConfig']['Tmpfs']
        for k in ts:
            self.options['kv'].append(
                {'--tmpfs': f"{k}:{ts[k]}"}
            )

    # --cidfile
    def cidfile(self):
        v = self.inspect['HostConfig']['ContainerIDFile']
        if v:
            self.options['kv'].append(
                {'--cidfile': v}
            )

    # --cpu-rt-period, Limit CPU real-time period in microseconds
    def cpu_rt_period(self):
        v: int = self.inspect['HostConfig']['CpuRealtimePeriod']
        if v != 0:
            self.options['kv'].append(
                {'--cpu-rt-period': v}
            )

    # --cpu-rt-runtime, Limit CPU real-time runtime in microseconds
    def cpu_rt_runtime(self):
        v: int = self.inspect['HostConfig']['CpuRealtimeRuntime']
        if v != 0:
            self.options['kv'].append(
                {'--cpu-rt-runtime': v}
            )

    # command and args
    def arguments(self):
        # 如果 self.inspect['Config']['Cmd'] 与 self.inspect_image['Config']['Cmd'] 不相同，说明docker run传参数了。
        if self.inspect['Config']['Cmd'] and (self.inspect['Config']['Cmd'] != self.inspect_image['Config']['Cmd']):
            self.args.append(self.inspect['Config']['Cmd'])

def main():
    if len(argv) < 2 or argv[1] == "--help":
        MYDOCKER.help_msg()
        exit(1)

    # 查看所有container的docker run命令
    elif argv[1] in ("{all}", "{allrun}"):
        # {all}: 包括shutdown的所有容器
        # {allrun}: 当前状态为run的所有容器
        is_all = False
        if argv[1] == "{all}":
            is_all = True

        for container in MYDOCKER().get_containers(is_all):
            print(f"=== container: {remve_prefix(container['Names'][0])} ===")
            try:
                MYDOCKER(container['Id']).start()
            except:
                pass
            print("\n")
    elif len(argv) > 2:
        for s in argv[1:]:
            print(f"=== container: {s} ===")
            try:
                MYDOCKER(s).start()
            except:
                pass
            print("\n")
    else:
        MYDOCKER(argv[1]).start()

if __name__ == '__main__':
    main()

# -*- coding: utf-8 -*- #!/usr/bin/env python # 解析docker 容器的启动参数  require: python 3  author: <NAME> mail: <EMAIL> date: 2021-09-24 存储单位转换      byte 转换 GB、MB、KB      :param size:     :return: 获取指定类的用户自定义方法      :param cls: class         类实例     :return: list        用户定义的方法名列表.        返回的list元素为方法(fundtion)，而非str型的方法名，是可以调用的方法对象。 驼峰字符转连接字符格式。      DriverOpts -> driver-opt      :param s:     :return: 十进制mode 转 user:group:other mode     0444  <-->  292     444 --> 100 100 100(2) --> 292(10)      :param num: 十进制数字     :return: ser:group:other mode         格式：0ugo # 数字转二进制字符串 list或dict对象转 initialization file 格式      :return: # 去掉最后一个"," 判断一个key是否存在于指定的字典中      :param key:     :param dic:     :param val: object         当key不存在时，新建的key对应的值     :return: 去掉不字符串指定的前缀      :param s:     :param prefix:     :return: # container name or container id. type is str # str # Options: container, service, stack :param all: bool             True: 所有容器，包括已经 shutdown的容器             False: 当前在run的容器         :return: 判断传入的实体类型          :return: Usage:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]          :return: str             运行容器的完整命令 #  key 型options # key-value 型options # _args = " ".join(self.args[0]) # sh -c “xxx” 命令中，-c 后的子命令需要引号包裹的情况 :return: # image 通过容器ID查询容器名          :param container_id:         :return: str             容器名 # image # 在本地没有此image的，将直接显示原来的image id. 或者有些直接指定就是image_id # parse options Usage: # Command alias echo "alias get_run_command='docker run --rm -v /var/run/docker.sock:/var/run/docker.sock cucker/get_command_4_run_container'" >> ~/.bashrc . ~/.bashrc  # Excute command ## For all runing containers get_run_command {allrun}  ## For all containers include shutdown get_run_command {all}  ## For one or more containers get_run_command <CONTAINER> [CONTAINER...] 从container inspect信息中解析docker run命令的options 和 arguments # --name # --rm, Automatically remove the container when it exits。 # --rm比较特殊，放在options['kv']的第一个。 这里把它当kv类型的option处理。 # --privileged # --hostname # --detach. Run container in background and print container ID # --tty, -t		Allocate a pseudo-TTY # --interactive, -i, Keep STDIN open even if not attached # --attach, -a		Attach to STDIN, STDOUT or STDERR # --restart, "no" is default. # --mount # 在image定义的 VOLUME # --volume, -v # --volumes-from, 挂载指定容器的数据卷 "VolumesFrom": [             "web_data:rw",             "nginx-2:Z"         ] # --publish, -p  Publish a container's port(s) to the host # --env, -e # 在容器inspect['Config']['Env']存在，且在镜像inspect_image['Config']['Env']不存在的元素，则为容器运行时添加的env "Env": [                 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",                 "NGINX_VERSION=1.19.1",                 "NJS_VERSION=0.4.2",                 "PKG_RELEASE=1~buster"             ]           -- inspect_image         "Env": [                 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",                 "NGINX_VERSION=1.21.1",                 "NJS_VERSION=0.6.1",                 "PKG_RELEASE=1~buster"             ] # --workdir, -w # --cpu-shares, -c # --cpu-period # --cpu-quota # --cpus # --memory, -m # --blkio-weight # --device-read-bps # --device-write-bps # --device-read-iops # --device-write-iops # --net, --network # joined容器网络（共用容器网络）== 共用容器网络 示例             {                 "HostConfig": {                     "NetworkMode": "container:8c9d8e9a4180f3e28c287202b4638a6ef9d26e639726b4a1140704d1ebab70b2"                     ...             }                 ... # 指定网络 == # --entrypoint # self.inspect['Config']['Entrypoint'] 为list # --dns # self.inspect['HostConfig']['Dns'] 为list # --dns-option # --dns-search # --domainname # --user , -u # --add-host # --volume-driver # --uts # --userns # --shm-size, Size of /dev/shm default is 64MB(67108864 byte). # --link. 用于连接其它容器。是比较老旧的技术，可以把要通行的几个容器连接到同一个单独的网络 来替代 # self.inspect['HostConfig']['Links'] 为null或[] # --ipc # --log-driver # --log-opt ## --log-driver # --log-opt # --label , -l # --init # inspect['HostConfig']['Init'] 为 bool值 # --stop-signal # --health-cmd # --health-interval # --health-retries # --health-start-period # --health-timeout # --no-healthcheck # --health-cmd # --no-healthcheck # --health-interval # --health-retries # --health-start-period # --health-timeout # --isolation # --device # --sysctl # --pid # --cap-add # --cap-drop # --ulimit # --cgroupns, Cgroup namespace to use (host|private|'') # --cgroup-parent # --tmpfs # --cidfile # --cpu-rt-period, Limit CPU real-time period in microseconds # --cpu-rt-runtime, Limit CPU real-time runtime in microseconds # command and args # 如果 self.inspect['Config']['Cmd'] 与 self.inspect_image['Config']['Cmd'] 不相同，说明docker run传参数了。 # 查看所有container的docker run命令 # {all}: 包括shutdown的所有容器 # {allrun}: 当前状态为run的所有容器