pyntcli 0.1.29__py3-none-any.whl → 0.1.31__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. build/lib/build/lib/build/lib/pyntcli/__init__.py +1 -0
  2. build/lib/build/lib/build/lib/pyntcli/analytics/__init__.py +0 -0
  3. build/lib/build/lib/build/lib/pyntcli/analytics/send.py +70 -0
  4. build/lib/build/lib/build/lib/pyntcli/auth/__init__.py +0 -0
  5. build/lib/build/lib/build/lib/pyntcli/auth/login.py +156 -0
  6. build/lib/build/lib/build/lib/pyntcli/commands/__init__.py +0 -0
  7. build/lib/build/lib/build/lib/pyntcli/commands/har.py +68 -0
  8. build/lib/build/lib/build/lib/pyntcli/commands/id_command.py +28 -0
  9. build/lib/build/lib/build/lib/pyntcli/commands/newman.py +85 -0
  10. build/lib/build/lib/build/lib/pyntcli/commands/postman.py +58 -0
  11. build/lib/build/lib/build/lib/pyntcli/commands/proxy.py +145 -0
  12. build/lib/build/lib/build/lib/pyntcli/commands/pynt_cmd.py +47 -0
  13. build/lib/build/lib/build/lib/pyntcli/commands/root.py +57 -0
  14. build/lib/build/lib/build/lib/pyntcli/commands/sub_command.py +15 -0
  15. build/lib/build/lib/build/lib/pyntcli/commands/util.py +26 -0
  16. build/lib/build/lib/build/lib/pyntcli/main.py +77 -0
  17. build/lib/build/lib/build/lib/pyntcli/pynt_docker/__init__.py +1 -0
  18. build/lib/build/lib/build/lib/pyntcli/pynt_docker/pynt_container.py +178 -0
  19. build/lib/build/lib/build/lib/pyntcli/store/__init__.py +1 -0
  20. build/lib/build/lib/build/lib/pyntcli/store/json_connector.py +23 -0
  21. build/lib/build/lib/build/lib/pyntcli/store/store.py +55 -0
  22. build/lib/build/lib/build/lib/pyntcli/store/store_connector.py +17 -0
  23. build/lib/build/lib/build/lib/pyntcli/ui/__init__.py +0 -0
  24. build/lib/build/lib/build/lib/pyntcli/ui/progress.py +31 -0
  25. build/lib/build/lib/build/lib/pyntcli/ui/ui_thread.py +168 -0
  26. build/lib/build/lib/build/lib/tests/auth/test_login.py +96 -0
  27. build/lib/build/lib/build/lib/tests/conftest.py +24 -0
  28. build/lib/build/lib/build/lib/tests/store/test_cred_store.py +11 -0
  29. build/lib/build/lib/pyntcli/__init__.py +1 -0
  30. build/lib/build/lib/pyntcli/analytics/__init__.py +0 -0
  31. build/lib/build/lib/pyntcli/analytics/send.py +70 -0
  32. build/lib/build/lib/pyntcli/auth/__init__.py +0 -0
  33. build/lib/build/lib/pyntcli/auth/login.py +156 -0
  34. build/lib/build/lib/pyntcli/commands/__init__.py +0 -0
  35. build/lib/build/lib/pyntcli/commands/har.py +68 -0
  36. build/lib/build/lib/pyntcli/commands/id_command.py +28 -0
  37. build/lib/build/lib/pyntcli/commands/newman.py +85 -0
  38. build/lib/build/lib/pyntcli/commands/postman.py +58 -0
  39. build/lib/build/lib/pyntcli/commands/proxy.py +147 -0
  40. build/lib/build/lib/pyntcli/commands/pynt_cmd.py +47 -0
  41. build/lib/build/lib/pyntcli/commands/root.py +57 -0
  42. build/lib/build/lib/pyntcli/commands/sub_command.py +15 -0
  43. build/lib/build/lib/pyntcli/commands/util.py +26 -0
  44. build/lib/build/lib/pyntcli/main.py +77 -0
  45. build/lib/build/lib/pyntcli/pynt_docker/__init__.py +1 -0
  46. build/lib/build/lib/pyntcli/pynt_docker/pynt_container.py +178 -0
  47. build/lib/build/lib/pyntcli/store/__init__.py +1 -0
  48. build/lib/build/lib/pyntcli/store/json_connector.py +23 -0
  49. build/lib/build/lib/pyntcli/store/store.py +55 -0
  50. build/lib/build/lib/pyntcli/store/store_connector.py +17 -0
  51. build/lib/build/lib/pyntcli/ui/__init__.py +0 -0
  52. build/lib/build/lib/pyntcli/ui/progress.py +31 -0
  53. build/lib/build/lib/pyntcli/ui/ui_thread.py +168 -0
  54. build/lib/build/lib/tests/auth/test_login.py +96 -0
  55. build/lib/build/lib/tests/conftest.py +24 -0
  56. build/lib/build/lib/tests/store/test_cred_store.py +11 -0
  57. build/lib/pyntcli/__init__.py +1 -1
  58. build/lib/pyntcli/commands/proxy.py +1 -0
  59. pyntcli/__init__.py +1 -1
  60. pyntcli/commands/proxy.py +0 -2
  61. {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/METADATA +1 -1
  62. pyntcli-0.1.31.dist-info/RECORD +117 -0
  63. pyntcli-0.1.29.dist-info/RECORD +0 -61
  64. {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/WHEEL +0 -0
  65. {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/entry_points.txt +0 -0
  66. {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,147 @@
1
+ import argparse
2
+ from copy import deepcopy
3
+ import os
4
+ import webbrowser
5
+ from http import HTTPStatus
6
+ import requests
7
+ import time
8
+ from subprocess import Popen, PIPE
9
+ from build.lib.pyntcli.commands import proxy
10
+
11
+ from pyntcli.store.store import CredStore
12
+ from pyntcli.pynt_docker import pynt_container
13
+ from pyntcli.ui import ui_thread
14
+ from pyntcli.ui.progress import connect_progress_ws, wrap_ws_progress
15
+ from pyntcli.commands import util, sub_command
16
+
17
+ def proxy_usage():
18
+ return ui_thread.PrinterText("Command integration to Pynt. Run a security scan with a given command.") \
19
+ .with_line("") \
20
+ .with_line("Usage:",style=ui_thread.PrinterText.HEADER) \
21
+ .with_line("\tpynt command [OPTIONS]") \
22
+ .with_line("") \
23
+ .with_line("Options:",style=ui_thread.PrinterText.HEADER) \
24
+ .with_line("\t--cmd - The command that runs the functional tests") \
25
+ .with_line("\t--port - Set the port pynt will listen to (DEFAULT: 5001)") \
26
+ .with_line("\t--allow-errors - If present will allow command to fail and continue execution") \
27
+ .with_line("\t--ca-path - The path to the CA file in PEM format") \
28
+ .with_line("\t--proxy-port - Set the port proxied traffic should be routed to (DEFAULT: 6666)") \
29
+ .with_line("\t--report - If present will save the generated report in this path.") \
30
+ .with_line("\t--insecure - use when target uses self signed certificates")
31
+
32
+
33
+ class ProxyCommand(sub_command.PyntSubCommand):
34
+ def __init__(self, name) -> None:
35
+ super().__init__(name)
36
+ self.scan_id = ""
37
+ self.proxy_sleep_interval = 2
38
+ self.proxy_healthcheck_buffer = 10
39
+ self.proxy_server_base_url = "http://localhost:{}/api"
40
+
41
+ def print_usage(self, *args):
42
+ ui_thread.print(proxy_usage())
43
+
44
+ def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
45
+ proxy_cmd = parent.add_parser(self.name)
46
+ proxy_cmd.add_argument("--port", "-p", help="", type=int, default=5001)
47
+ proxy_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
48
+ proxy_cmd.add_argument("--cmd", help="", default="", required=True)
49
+ proxy_cmd.add_argument("--allow-errors", action="store_true")
50
+ proxy_cmd.add_argument("--ca-path", type=str, default="")
51
+ proxy_cmd.add_argument("--report", type=str, default="")
52
+ proxy_cmd.print_usage = self.print_usage
53
+ proxy_cmd.print_help = self.print_usage
54
+ return proxy_cmd
55
+
56
+ def _updated_environment(self, args):
57
+ env_copy = deepcopy(os.environ)
58
+ return env_copy.update({"HTTP_PROXY": "http://localhost:{}".format(args.proxy_port),
59
+ "HTTPS_PROXY": "http://localhost:{}".format(args.proxy_port)})
60
+
61
+ def _start_proxy(self, args):
62
+ res = requests.put(self.proxy_server_base_url.format(args.port) + "/proxy/start")
63
+ res.raise_for_status()
64
+ self.scan_id = res.json()["scanId"]
65
+
66
+ def _stop_proxy(self, args):
67
+ start = time.time()
68
+ while start + self.proxy_healthcheck_buffer > time.time():
69
+ res = requests.put(self.proxy_server_base_url.format(args.port) + "/proxy/stop", json={"scanId": self.scan_id})
70
+ if res.status_code == HTTPStatus.OK:
71
+ return
72
+ time.sleep(self.proxy_sleep_interval)
73
+ raise TimeoutError()
74
+
75
+ def _get_report(self, args):
76
+ while True:
77
+ res = requests.get(self.proxy_server_base_url.format(args.port) + "/report", params={"scanId": self.scan_id})
78
+ if res.status_code == HTTPStatus.OK:
79
+ return res.text
80
+ if res.status_code == HTTPStatus.ACCEPTED:
81
+ time.sleep(self.proxy_sleep_interval)
82
+ continue
83
+ if res.status_code == 517: #pynt did not recieve any requests
84
+ ui_thread.print(ui_thread.PrinterText(res.json()["message"], ui_thread.PrinterText.WARNING))
85
+ return
86
+ ui_thread.print("Error in polling for scan report: {}".format(res.text))
87
+ return
88
+
89
+ def run_cmd(self, args: argparse.Namespace):
90
+ docker_type, docker_arguments = pynt_container.get_container_with_arguments(pynt_container.PyntDockerPort(args.port, args.port, "--port"),
91
+ pynt_container.PyntDockerPort(args.proxy_port, args.proxy_port, "--proxy-port"))
92
+
93
+ if "insecure" in args and args.insecure:
94
+ docker_arguments.append("--insecure")
95
+
96
+ if "dev_flags" in args:
97
+ docker_arguments += args.dev_flags.split(" ")
98
+
99
+ mounts = []
100
+ if "ca_path" in args and args.ca_path:
101
+ if not os.path.isfile(args.ca_path):
102
+ ui_thread.print(ui_thread.PrinterText("Could not find the provided ca path, please provide with a valid path", ui_thread.PrinterText.WARNING))
103
+ return
104
+
105
+ ca_name = os.path.basename(args.ca_path)
106
+ docker_arguments += ["--ca-path", ca_name]
107
+ mounts.append(pynt_container.create_mount(os.path.abspath(args.ca_path), "/etc/pynt/{}".format(ca_name)))
108
+
109
+ creds_path = CredStore().get_path()
110
+ mounts.append(pynt_container.create_mount(creds_path, "/app/creds.json"))
111
+
112
+ proxy_docker = pynt_container.PyntContainer(image_name=pynt_container.PYNT_DOCKER_IMAGE,
113
+ tag="proxy-latest",
114
+ mounts=mounts,
115
+ detach=True,
116
+ args=docker_arguments)
117
+ proxy_docker.run(docker_type)
118
+ ui_thread.print_generator(proxy_docker.stdout)
119
+
120
+ util.wait_for_healthcheck("http://localhost:{}".format(args.port))
121
+ self._start_proxy(args)
122
+
123
+ user_process = Popen(args.cmd, shell=True, stdout=PIPE, stderr=PIPE, env=self._updated_environment(args))
124
+ ui_thread.print_generator(user_process.stdout)
125
+ ui_thread.print_generator(user_process.stderr)
126
+ rc = user_process.wait()
127
+ if rc != 0 and not args.allow_errors:
128
+ proxy_docker.stop()
129
+ ui_thread.print(ui_thread.PrinterText("Command finished with error return code {}, If you wish Pynt to run anyway, run with --allow-errors".format(rc)))
130
+ return
131
+
132
+ self._stop_proxy(args)
133
+
134
+ report = ""
135
+ with ui_thread.progress(wrap_ws_progress(connect_progress_ws("ws://localhost:{}/progress?scanId={}".format(args.port, self.scan_id))), "scan in progress..."):
136
+ report = self._get_report(args)
137
+ if not report:
138
+ proxy_docker.stop()
139
+ return
140
+ report_path = os.path.join(os.getcwd(), "report_{}.html".format(int(time.time())))
141
+ if "report" in args and args.report:
142
+ report_path = os.path.abspath(args.report)
143
+ with open(report_path, "w") as f:
144
+ f.write(report)
145
+
146
+ webbrowser.open("file://{}".format(report_path))
147
+ proxy_docker.stop()
@@ -0,0 +1,47 @@
1
+ import argparse
2
+ from typing import Dict, List
3
+
4
+ from . import postman, root, sub_command, id_command, proxy , newman, har
5
+
6
+ avail_sub_commands = [
7
+ postman.PostmanSubCommand("postman"),
8
+ id_command.PyntShowIdCommand("pynt-id"),
9
+ newman.NewmanSubCommand("newman"),
10
+ har.HarSubCommand("har"),
11
+ proxy.ProxyCommand("command"),
12
+ ]
13
+
14
+ class PyntCommandException(Exception):
15
+ pass
16
+
17
+ class BadArgumentsException(PyntCommandException):
18
+ pass
19
+
20
+ class NoSuchCommandException(PyntCommandException):
21
+ pass
22
+
23
+
24
+ class PyntCommand:
25
+ def __init__(self) -> None:
26
+ self.base: root.BaseCommand = root.BaseCommand()
27
+ self.sub_commands: Dict[str, sub_command.PyntSubCommand] = {sc.get_name(): sc for sc in avail_sub_commands}
28
+ self._start_command()
29
+
30
+ def _start_command(self):
31
+ self.base.cmd()
32
+ for sc in self.sub_commands.values():
33
+ self.base.add_base_arguments(sc.add_cmd(self.base.get_subparser()))
34
+
35
+ def parse_args(self, args_from_cmd: List[str]):
36
+ return self.base.cmd().parse_args(args_from_cmd)
37
+
38
+ def run_cmd(self, args: argparse.Namespace):
39
+ if not "command" in args:
40
+ raise BadArgumentsException()
41
+
42
+ command = getattr(args, "command")
43
+ if not command in self.sub_commands:
44
+ raise NoSuchCommandException()
45
+
46
+ self.base.run_cmd(args)
47
+ self.sub_commands[command].run_cmd(args)
@@ -0,0 +1,57 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from pyntcli.auth import login
5
+ from pyntcli.ui import ui_thread
6
+ from pyntcli.analytics import send as analytics
7
+
8
+ def root_usage():
9
+ return ui_thread.PrinterText("Usage:",style=ui_thread.PrinterText.HEADER) \
10
+ .with_line("\tpynt [COMMAND] [OPTIONS]") \
11
+ .with_line("") \
12
+ .with_line("Commands:",style=ui_thread.PrinterText.HEADER) \
13
+ .with_line("\tpostman - integration with postman, run scan from pynt postman collection") \
14
+ .with_line("\tnewman - run postman collection from the CLI") \
15
+ .with_line("\tpynt-id - view your pynt-id to use when running pynt in CI pipeline") \
16
+ .with_line("") \
17
+ .with_line("Run pynt [COMMAND] -h to get help on a specific command",style=ui_thread.PrinterText.INFO)
18
+
19
+ def usage(*args):
20
+ ui_thread.print(root_usage())
21
+
22
+ class BaseCommand():
23
+ def __init__(self) -> None:
24
+ self.base: argparse.ArgumentParser = None
25
+ self.subparser: argparse._SubParsersAction = None
26
+
27
+ def cmd(self):
28
+ if self.base:
29
+ return self.base
30
+
31
+ self.base = argparse.ArgumentParser(prog="pynt")
32
+ self.base.print_usage = usage
33
+ self.base.print_help = usage
34
+ return self.base
35
+
36
+ def add_base_arguments(self, parser):
37
+ parser.add_argument("--insecure", default=False, required=False, action='store_true',help="use when target uses self signed certificates")
38
+ parser.add_argument("--dev-flags", type=str,default="", help=argparse.SUPPRESS)
39
+
40
+ def get_subparser(self) -> argparse._SubParsersAction:
41
+ if self.subparser is None:
42
+ self.subparser = self.base.add_subparsers(help="", dest="command")
43
+
44
+ return self.subparser
45
+
46
+ def run_cmd(self, args: argparse.Namespace):
47
+ analytics.emit(analytics.LOGIN_START)
48
+
49
+ if login.should_login():
50
+ l = login.Login().login()
51
+ else:
52
+ login.refresh_token()
53
+
54
+ analytics.emit(analytics.LOGIN_DONE)
55
+ user_id = login.user_id()
56
+ if user_id:
57
+ analytics.set_user_id(user_id)
@@ -0,0 +1,15 @@
1
+ import argparse
2
+
3
+ class PyntSubCommand:
4
+ def __init__(self, name) -> None:
5
+ self.name = name
6
+ pass
7
+
8
+ def get_name(self):
9
+ return self.name
10
+
11
+ def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
12
+ raise NotImplemented()
13
+
14
+ def run_cmd(self, args: argparse.Namespace):
15
+ raise NotImplemented()
@@ -0,0 +1,26 @@
1
+ import requests
2
+ import time
3
+ import socket
4
+
5
+ def is_port_in_use(port: int) -> bool:
6
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
7
+ return s.connect_ex(('localhost', port)) == 0
8
+
9
+ def find_open_port() -> int:
10
+ with socket.socket() as s:
11
+ s.bind(('', 0))
12
+ return s.getsockname()[1]
13
+
14
+ HEALTHCHECK_TIMEOUT = 10
15
+ HEALTHCHECK_INTERVAL = 2
16
+
17
+ def wait_for_healthcheck(address):
18
+ start = time.time()
19
+ while start + HEALTHCHECK_TIMEOUT > time.time():
20
+ try:
21
+ res = requests.get(address + "/healthcheck")
22
+ if res.status_code == 418:
23
+ return
24
+ except:
25
+ time.sleep(HEALTHCHECK_INTERVAL)
26
+ raise TimeoutError()
@@ -0,0 +1,77 @@
1
+ from sys import argv, exit
2
+ import signal
3
+
4
+ from pyntcli import __version__ as cli_version
5
+ from pyntcli.commands import pynt_cmd
6
+ from pyntcli.pynt_docker import pynt_container
7
+ from pyntcli.ui import ui_thread
8
+ from pyntcli.auth import login
9
+ from pyntcli.analytics import send as analytics
10
+
11
+ def signal_handler(signal_number, frame):
12
+ ui_thread.print(ui_thread.PrinterText("Exiting..."))
13
+
14
+ analytics.stop()
15
+ pynt_container.PyntContainerRegistery.instance().stop_all_containers()
16
+ ui_thread.stop()
17
+
18
+ exit(0)
19
+
20
+ import requests
21
+ from distutils.version import StrictVersion
22
+ VERSION_CHECK_URL = "https://pypi.org/pypi/pyntcli/json"
23
+
24
+ def check_is_latest_version(current_version):
25
+ res = requests.get(VERSION_CHECK_URL)
26
+ res.raise_for_status()
27
+
28
+ avail_versions = list(res.json()["releases"].keys())
29
+ avail_versions.sort(key=StrictVersion)
30
+
31
+ if current_version != avail_versions[-1]:
32
+ ui_thread.print(ui_thread.PrinterText("""Pynt CLI new version is available, upgrade now with:
33
+ python3 -m pip install --upgrade pyntcli""",ui_thread.PrinterText.WARNING))
34
+
35
+ def check_for_dependecies():
36
+ pynt_container.get_docker_type()
37
+
38
+ def print_header():
39
+ ui_thread.print(ui_thread.PrinterText(*ui_thread.pynt_header()) \
40
+ .with_line(*ui_thread.pynt_version()) \
41
+ .with_line(""))
42
+ def start_analytics():
43
+ user_id = login.user_id()
44
+ if user_id:
45
+ analytics.set_user_id(user_id)
46
+
47
+ analytics.emit(analytics.CLI_START)
48
+
49
+ def main():
50
+ print_header()
51
+ try:
52
+ start_analytics()
53
+ check_for_dependecies()
54
+ check_is_latest_version(cli_version)
55
+ signal.signal(signal.SIGINT, signal_handler)
56
+ cli = pynt_cmd.PyntCommand()
57
+ cli.run_cmd(cli.parse_args(argv[1:]))
58
+ analytics.stop()
59
+ except pynt_cmd.PyntCommandException:
60
+ pynt_cmd.root.usage()
61
+ except pynt_container.DockerNotAvailableException:
62
+ ui_thread.print(ui_thread.PrinterText("Docker was unavailable, please make sure docker is installed and running.", ui_thread.PrinterText.WARNING))
63
+ analytics.emit(analytics.ERROR, {"error": "docker unavailable"})
64
+ except requests.exceptions.SSLError:
65
+ ui_thread.print(ui_thread.PrinterText("We encountered SSL issues and could not proceed, this may be the cause of a VPN or a Firewall in place.", ui_thread.PrinterText.WARNING))
66
+ analytics.emit(analytics.ERROR, {"error": "ssl issue"})
67
+ except login.Timeout:
68
+ ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to incomplete registration, please try again.", ui_thread.PrinterText.WARNING))
69
+ analytics.emit(analytics.ERROR, {"error": "login timeout"})
70
+ except login.InvalidTokenInEnvVarsException:
71
+ ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to malformed credentials provided in env vars.", ui_thread.PrinterText.WARNING))
72
+ analytics.emit(analytics.ERROR, {"error": "invalid pynt cli credentials in env vars"})
73
+ finally:
74
+ ui_thread.stop()
75
+
76
+ if __name__ == "__main__":
77
+ main()
@@ -0,0 +1 @@
1
+ from .pynt_container import *
@@ -0,0 +1,178 @@
1
+ import docker
2
+ from docker.errors import DockerException
3
+ from docker.types import Mount
4
+ import os
5
+ from typing import List
6
+
7
+ from pyntcli.ui import ui_thread
8
+
9
+ PYNT_DOCKER_IMAGE = "ghcr.io/pynt-io/pynt"
10
+
11
+ def create_mount(src, destination, mount_type="bind"):
12
+ return Mount(target=destination, source=src, type=mount_type)
13
+
14
+ class DockerNotAvailableException(Exception):
15
+ pass
16
+
17
+ def get_docker_type():
18
+ try:
19
+ c = docker.from_env()
20
+ version_data = c.version()
21
+ platform = version_data.get("Platform")
22
+
23
+ if platform and platform.get("Name"):
24
+ return platform.get("Name")
25
+
26
+ return ""
27
+
28
+ except DockerException:
29
+ raise DockerNotAvailableException()
30
+ except Exception: #TODO: This is since windows is not behaving nice
31
+ raise DockerNotAvailableException()
32
+
33
+ class PyntDockerPort:
34
+ def __init__(self, src, dest, name) -> None:
35
+ self.src = src
36
+ self.dest = dest
37
+ self.name = name
38
+
39
+ def get_container_with_arguments(*args: PyntDockerPort):
40
+ if "desktop" in get_docker_type().lower():
41
+ ports = {}
42
+ for p in args:
43
+ ports[str(p.src)] = int(p.dest)
44
+ docker_type = PyntDockerDesktopContainer(ports=ports)
45
+ return docker_type , []
46
+
47
+ docker_arguments = []
48
+ docker_type = PyntNativeContainer(network="host")
49
+
50
+ for p in args:
51
+ docker_arguments.append(p.name)
52
+ docker_arguments.append(str(p.dest))
53
+
54
+ return docker_type, docker_arguments
55
+
56
+ def _container_image_from_tag(tag: str) -> str:
57
+ if ":" in tag:
58
+ return tag.split(":")[0]
59
+
60
+ return tag
61
+
62
+ class PyntContainer():
63
+ def __init__(self, image_name, tag, mounts, detach, args) -> None:
64
+ self.docker_client: docker.DockerClient = None
65
+ self.image = image_name if not os.environ.get("IMAGE") else os.environ.get("IMAGE")
66
+ self.tag = tag if not os.environ.get("TAG") else os.environ.get("TAG")
67
+ self.mounts = mounts
68
+ self.detach = detach
69
+ self.stdout = None
70
+ self.running = False
71
+ self.args = args
72
+ self.container_name = ""
73
+
74
+ def _create_docker_client(self):
75
+ self.docker_client = docker.from_env()
76
+ pat = os.environ.get("DOCKER_PASSWORD")
77
+ username = os.environ.get("DOCKER_USERNAME")
78
+ registry = os.environ.get("DOCKER_REGISTRY")
79
+ if pat and username and registry:
80
+ self.docker_client.login(username=username, password=pat, registry=registry)
81
+
82
+ def _is_docker_image_up_to_date(self, image):
83
+ return True
84
+
85
+ def _handle_outdated_docker_image(self, image):
86
+ return image
87
+
88
+ def kill_other_instances(self):
89
+ for c in self.docker_client.containers.list():
90
+ if _container_image_from_tag(c.image.tags[0]) == self.image:
91
+ c.kill()
92
+
93
+ def stop(self):
94
+ if not self.running:
95
+ return
96
+
97
+ self.kill_other_instances()
98
+
99
+ self.docker_client.close()
100
+ self.docker_client = None
101
+ self.running = False
102
+
103
+ def is_alive(self):
104
+ if not self.docker_client or not self.container_name:
105
+ return False
106
+
107
+ l = self.docker_client.containers.list(filters={"name": self.container_name})
108
+ if len(l) != 1:
109
+ return False
110
+
111
+ return l[0].status == "running"
112
+
113
+ def run(self, integration_docker):
114
+ if not self.docker_client:
115
+ self._create_docker_client()
116
+
117
+ self.running = True
118
+ self.kill_other_instances()
119
+
120
+ ui_thread.print(ui_thread.PrinterText("Pulling latest docker", ui_thread.PrinterText.INFO))
121
+ image = self.docker_client.images.pull(self.image, tag=self.tag)
122
+ if not self._is_docker_image_up_to_date(image):
123
+ image = self._handle_outdated_docker_image(image)
124
+ ui_thread.print(ui_thread.PrinterText("Docker pull done", ui_thread.PrinterText.INFO))
125
+
126
+ args = self.args if self.args else None
127
+
128
+ run_arguments = {
129
+ "image":image,
130
+ "detach":self.detach,
131
+ "mounts":self.mounts,
132
+ "stream": True,
133
+ "remove": True,
134
+ "command": args
135
+ }
136
+
137
+ run_arguments.update(integration_docker.get_argumets())
138
+
139
+ c = self.docker_client.containers.run(**run_arguments)
140
+ self.container_name = c.name
141
+ self.stdout = c.logs(stream=True)
142
+
143
+ PyntContainerRegistery.instance().register_container(self)
144
+
145
+ class PyntDockerDesktopContainer():
146
+ def __init__(self, ports) -> None:
147
+ self.ports = ports
148
+
149
+ def get_argumets(self):
150
+ return {"ports": self.ports} if self.ports else {}
151
+
152
+ class PyntNativeContainer():
153
+ def __init__(self, network) -> None:
154
+ self.network = network
155
+
156
+ def get_argumets(self):
157
+ return {"network": self.network} if self.network else {}
158
+
159
+
160
+ class PyntContainerRegistery():
161
+ _instance = None
162
+
163
+ def __init__(self) -> None:
164
+ self.containers: List[PyntContainer] = []
165
+
166
+ @staticmethod
167
+ def instance():
168
+ if not PyntContainerRegistery._instance:
169
+ PyntContainerRegistery._instance = PyntContainerRegistery()
170
+
171
+ return PyntContainerRegistery._instance
172
+
173
+ def register_container(self, c: PyntContainer):
174
+ self.containers.append(c)
175
+
176
+ def stop_all_containers(self):
177
+ for c in self.containers:
178
+ c.stop()
@@ -0,0 +1 @@
1
+ from .store import CredStore
@@ -0,0 +1,23 @@
1
+ import json
2
+
3
+ from .store_connector import StoreConnector
4
+
5
+ class JsonStoreConnector(StoreConnector):
6
+ def __init__(self, data) -> None:
7
+ if not data:
8
+ data = JsonStoreConnector.default_value()
9
+ super().__init__(data)
10
+ self.json = json.loads(self._data)
11
+
12
+ def get(self, key):
13
+ return self.json.get(key, None)
14
+
15
+ def put(self, key, value):
16
+ self.json[key] = value
17
+
18
+ def dump(self):
19
+ return json.dumps(self.json, indent=2)
20
+
21
+ @staticmethod
22
+ def default_value():
23
+ return json.dumps({})
@@ -0,0 +1,55 @@
1
+ from typing import Type
2
+ import os
3
+ import platform
4
+
5
+ from .json_connector import JsonStoreConnector
6
+ from .store_connector import StoreConnector
7
+
8
+ class Store():
9
+ def __init__(self, file_location: str, connector_type: Type[StoreConnector]) -> None :
10
+ self.file_location = file_location
11
+ self.connector:StoreConnector = None
12
+ self._file = None
13
+ self._connector_tpye = connector_type
14
+
15
+ def _get_file_data(self):
16
+ if self.connector:
17
+ return
18
+
19
+ dirname = os.path.dirname(self.file_location)
20
+ if not os.path.exists(dirname):
21
+ os.makedirs(dirname)
22
+
23
+ if not os.path.exists(self.file_location):
24
+ with open(self.file_location, "w") as f:
25
+ self.connector = self._connector_tpye(self._connector_tpye.default_value())
26
+ return
27
+
28
+ with open(self.file_location, "r+") as f:
29
+ self.connector = self._connector_tpye(f.read())
30
+
31
+ def get(self, key):
32
+ self._get_file_data()
33
+ return self.connector.get(key)
34
+
35
+ def put(self, key, value):
36
+ self._get_file_data()
37
+ self.connector.put(key, value)
38
+
39
+ def get_path(self):
40
+ return self.file_location
41
+
42
+ def __enter__(self):
43
+ self._get_file_data()
44
+ return self
45
+
46
+ def __exit__(self, type, value, traceback):
47
+ with open(self.file_location, "w") as f:
48
+ f.write(self.connector.dump())
49
+
50
+ class CredStore(Store):
51
+ def __init__(self) -> None:
52
+ dir = ".pynt"
53
+ super().__init__(file_location=os.path.join(os.path.expanduser("~"), dir, "creds.json"),
54
+ connector_type=JsonStoreConnector)
55
+
@@ -0,0 +1,17 @@
1
+ class StoreConnector():
2
+ def __init__(self, data) -> None:
3
+ self._data = data
4
+
5
+ def get(self, key):
6
+ raise NotImplemented()
7
+
8
+ def put(self, key, value):
9
+ raise NotImplementedError()
10
+
11
+ def dump(self):
12
+ raise NotImplementedError()
13
+
14
+ @classmethod
15
+ def default_value(cls):
16
+ raise NotImplementedError()
17
+
File without changes
@@ -0,0 +1,31 @@
1
+ import websocket
2
+
3
+ def connect_progress_ws(address):
4
+ ws = websocket.WebSocket()
5
+ ws.connect(address)
6
+ return ws
7
+
8
+ def wrap_ws_progress(ws):
9
+ prev = 0
10
+ while ws.connected:
11
+ try:
12
+ current = int(ws.recv())
13
+ yield current - prev
14
+ prev = current
15
+ except websocket.WebSocketConnectionClosedException:
16
+ return
17
+
18
+ class PyntProgress():
19
+ def __init__(self, what_to_track, total, description) -> None:
20
+ self.running = False
21
+ self.trackable = what_to_track
22
+ self.total = total
23
+ self.description = description
24
+
25
+ def __enter__(self):
26
+ return self
27
+
28
+ def __exit__(self, exc_type, exc_value, exc_traceback):
29
+ self.running = False
30
+
31
+