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.
- build/lib/build/lib/build/lib/pyntcli/__init__.py +1 -0
- build/lib/build/lib/build/lib/pyntcli/analytics/__init__.py +0 -0
- build/lib/build/lib/build/lib/pyntcli/analytics/send.py +70 -0
- build/lib/build/lib/build/lib/pyntcli/auth/__init__.py +0 -0
- build/lib/build/lib/build/lib/pyntcli/auth/login.py +156 -0
- build/lib/build/lib/build/lib/pyntcli/commands/__init__.py +0 -0
- build/lib/build/lib/build/lib/pyntcli/commands/har.py +68 -0
- build/lib/build/lib/build/lib/pyntcli/commands/id_command.py +28 -0
- build/lib/build/lib/build/lib/pyntcli/commands/newman.py +85 -0
- build/lib/build/lib/build/lib/pyntcli/commands/postman.py +58 -0
- build/lib/build/lib/build/lib/pyntcli/commands/proxy.py +145 -0
- build/lib/build/lib/build/lib/pyntcli/commands/pynt_cmd.py +47 -0
- build/lib/build/lib/build/lib/pyntcli/commands/root.py +57 -0
- build/lib/build/lib/build/lib/pyntcli/commands/sub_command.py +15 -0
- build/lib/build/lib/build/lib/pyntcli/commands/util.py +26 -0
- build/lib/build/lib/build/lib/pyntcli/main.py +77 -0
- build/lib/build/lib/build/lib/pyntcli/pynt_docker/__init__.py +1 -0
- build/lib/build/lib/build/lib/pyntcli/pynt_docker/pynt_container.py +178 -0
- build/lib/build/lib/build/lib/pyntcli/store/__init__.py +1 -0
- build/lib/build/lib/build/lib/pyntcli/store/json_connector.py +23 -0
- build/lib/build/lib/build/lib/pyntcli/store/store.py +55 -0
- build/lib/build/lib/build/lib/pyntcli/store/store_connector.py +17 -0
- build/lib/build/lib/build/lib/pyntcli/ui/__init__.py +0 -0
- build/lib/build/lib/build/lib/pyntcli/ui/progress.py +31 -0
- build/lib/build/lib/build/lib/pyntcli/ui/ui_thread.py +168 -0
- build/lib/build/lib/build/lib/tests/auth/test_login.py +96 -0
- build/lib/build/lib/build/lib/tests/conftest.py +24 -0
- build/lib/build/lib/build/lib/tests/store/test_cred_store.py +11 -0
- build/lib/build/lib/pyntcli/__init__.py +1 -0
- build/lib/build/lib/pyntcli/analytics/__init__.py +0 -0
- build/lib/build/lib/pyntcli/analytics/send.py +70 -0
- build/lib/build/lib/pyntcli/auth/__init__.py +0 -0
- build/lib/build/lib/pyntcli/auth/login.py +156 -0
- build/lib/build/lib/pyntcli/commands/__init__.py +0 -0
- build/lib/build/lib/pyntcli/commands/har.py +68 -0
- build/lib/build/lib/pyntcli/commands/id_command.py +28 -0
- build/lib/build/lib/pyntcli/commands/newman.py +85 -0
- build/lib/build/lib/pyntcli/commands/postman.py +58 -0
- build/lib/build/lib/pyntcli/commands/proxy.py +147 -0
- build/lib/build/lib/pyntcli/commands/pynt_cmd.py +47 -0
- build/lib/build/lib/pyntcli/commands/root.py +57 -0
- build/lib/build/lib/pyntcli/commands/sub_command.py +15 -0
- build/lib/build/lib/pyntcli/commands/util.py +26 -0
- build/lib/build/lib/pyntcli/main.py +77 -0
- build/lib/build/lib/pyntcli/pynt_docker/__init__.py +1 -0
- build/lib/build/lib/pyntcli/pynt_docker/pynt_container.py +178 -0
- build/lib/build/lib/pyntcli/store/__init__.py +1 -0
- build/lib/build/lib/pyntcli/store/json_connector.py +23 -0
- build/lib/build/lib/pyntcli/store/store.py +55 -0
- build/lib/build/lib/pyntcli/store/store_connector.py +17 -0
- build/lib/build/lib/pyntcli/ui/__init__.py +0 -0
- build/lib/build/lib/pyntcli/ui/progress.py +31 -0
- build/lib/build/lib/pyntcli/ui/ui_thread.py +168 -0
- build/lib/build/lib/tests/auth/test_login.py +96 -0
- build/lib/build/lib/tests/conftest.py +24 -0
- build/lib/build/lib/tests/store/test_cred_store.py +11 -0
- build/lib/pyntcli/__init__.py +1 -1
- build/lib/pyntcli/commands/proxy.py +1 -0
- pyntcli/__init__.py +1 -1
- pyntcli/commands/proxy.py +0 -2
- {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/METADATA +1 -1
- pyntcli-0.1.31.dist-info/RECORD +117 -0
- pyntcli-0.1.29.dist-info/RECORD +0 -61
- {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/WHEEL +0 -0
- {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/entry_points.txt +0 -0
- {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from threading import Thread
|
|
2
|
+
import queue
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from rich.text import Text
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.status import Status
|
|
8
|
+
from rich.progress import Progress
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
|
|
11
|
+
from pyntcli import __version__ as cli_version
|
|
12
|
+
from pyntcli.ui.progress import PyntProgress
|
|
13
|
+
|
|
14
|
+
class PrinterText():
|
|
15
|
+
DEFAULT = 0
|
|
16
|
+
HEADER = 1
|
|
17
|
+
INFO = 2
|
|
18
|
+
WARNING = 3
|
|
19
|
+
|
|
20
|
+
def __init__(self,text,style=DEFAULT):
|
|
21
|
+
self.text = Text(text, PrinterText.get_style(style))
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def get_style(style):
|
|
25
|
+
if style == PrinterText.INFO:
|
|
26
|
+
return "bold blue"
|
|
27
|
+
if style == PrinterText.WARNING:
|
|
28
|
+
return "bold red"
|
|
29
|
+
if style == PrinterText.HEADER:
|
|
30
|
+
return "bold"
|
|
31
|
+
if style == PrinterText.DEFAULT:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
def with_line(self, line, style=DEFAULT):
|
|
35
|
+
self.text.append(os.linesep)
|
|
36
|
+
self.text.append(Text(line, PrinterText.get_style(style)))
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
class AnsiText():
|
|
40
|
+
def __init__(self, data) -> None:
|
|
41
|
+
self.data = data
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def wrap_gen(gen):
|
|
45
|
+
for v in gen:
|
|
46
|
+
yield AnsiText(v)
|
|
47
|
+
|
|
48
|
+
class Spinner():
|
|
49
|
+
def __init__(self, prompt, style) -> None:
|
|
50
|
+
self.prompt = prompt
|
|
51
|
+
self.style = style
|
|
52
|
+
self.runnning = False
|
|
53
|
+
|
|
54
|
+
def __enter__(self):
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
58
|
+
self.running = False
|
|
59
|
+
|
|
60
|
+
def pynt_version()-> Tuple[str,int]:
|
|
61
|
+
return "Pynt CLI version " + cli_version,PrinterText.DEFAULT
|
|
62
|
+
|
|
63
|
+
def pynt_header()-> Tuple[str,int]:
|
|
64
|
+
return "API Security testing autopilot",PrinterText.DEFAULT
|
|
65
|
+
|
|
66
|
+
def gen_func_loop(gen):
|
|
67
|
+
for l in gen:
|
|
68
|
+
data = l
|
|
69
|
+
if type(data) == bytes:
|
|
70
|
+
data = l.decode("utf-8")
|
|
71
|
+
if not isinstance(data, AnsiText) and data[-1] == "\n":
|
|
72
|
+
data = data[:-1]
|
|
73
|
+
_print(data)
|
|
74
|
+
|
|
75
|
+
def print_generator(gen):
|
|
76
|
+
t = Thread(target=gen_func_loop, args=(gen,), daemon=True)
|
|
77
|
+
t.start()
|
|
78
|
+
|
|
79
|
+
def _print(s):
|
|
80
|
+
Printer.instance().print(s)
|
|
81
|
+
|
|
82
|
+
def print(s):
|
|
83
|
+
if type(s) == bytes:
|
|
84
|
+
s = s.decode("utf-8")
|
|
85
|
+
_print(s)
|
|
86
|
+
|
|
87
|
+
def stop():
|
|
88
|
+
Printer.instance().stop()
|
|
89
|
+
|
|
90
|
+
class Printer():
|
|
91
|
+
_instace = None
|
|
92
|
+
|
|
93
|
+
def __init__(self) -> None:
|
|
94
|
+
self.running = False
|
|
95
|
+
self.run_thread = Thread(target=self._print_in_loop, daemon=True)
|
|
96
|
+
self.print_queue = queue.Queue()
|
|
97
|
+
self.console = Console(tab_size=4)
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def instance():
|
|
101
|
+
if not Printer._instace:
|
|
102
|
+
Printer._instace = Printer()
|
|
103
|
+
Printer._instace.start()
|
|
104
|
+
|
|
105
|
+
return Printer._instace
|
|
106
|
+
|
|
107
|
+
def start(self):
|
|
108
|
+
self.running = True
|
|
109
|
+
self.run_thread.start()
|
|
110
|
+
|
|
111
|
+
def _handle_spinner(self, spinner):
|
|
112
|
+
spinner.running = True
|
|
113
|
+
s = Status(spinner.prompt, spinner=spinner.style, console=self.console)
|
|
114
|
+
s.start()
|
|
115
|
+
while spinner.running and self.running:
|
|
116
|
+
time.sleep(0.5)
|
|
117
|
+
s.stop()
|
|
118
|
+
|
|
119
|
+
def _handle_progress(self, progress):
|
|
120
|
+
progress.running = True
|
|
121
|
+
with Progress(console=self.console, transient=True) as p:
|
|
122
|
+
t = p.add_task(description=progress.description, total=progress.total)
|
|
123
|
+
for update in progress.trackable:
|
|
124
|
+
if not (progress.running and self.running):
|
|
125
|
+
return
|
|
126
|
+
p.update(t, advance=update)
|
|
127
|
+
time.sleep(0.5)
|
|
128
|
+
|
|
129
|
+
def _print_in_loop(self):
|
|
130
|
+
while self.running:
|
|
131
|
+
try:
|
|
132
|
+
data = self.print_queue.get(timeout=1)
|
|
133
|
+
if isinstance(data, list):
|
|
134
|
+
data = data[0]
|
|
135
|
+
if isinstance(data, Spinner):
|
|
136
|
+
self._handle_spinner(data)
|
|
137
|
+
if isinstance(data, PyntProgress):
|
|
138
|
+
self._handle_progress(data)
|
|
139
|
+
else:
|
|
140
|
+
self.console.print(data)
|
|
141
|
+
except queue.Empty:
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
while not self.print_queue.empty():
|
|
145
|
+
self.console.print(self.print_queue.get())
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def print(self, data):
|
|
149
|
+
if isinstance(data,PrinterText):
|
|
150
|
+
data = data.text
|
|
151
|
+
if isinstance(data, AnsiText):
|
|
152
|
+
data = Text.from_ansi(data.data.decode())
|
|
153
|
+
self.print_queue.put(data)
|
|
154
|
+
|
|
155
|
+
def stop(self):
|
|
156
|
+
self.running = False
|
|
157
|
+
self.run_thread.join()
|
|
158
|
+
|
|
159
|
+
def spinner(prompt, style):
|
|
160
|
+
s = Spinner(prompt, style)
|
|
161
|
+
_print([s])
|
|
162
|
+
return s
|
|
163
|
+
|
|
164
|
+
def progress(what_to_track, description, total=100):
|
|
165
|
+
p = PyntProgress(what_to_track, total, description)
|
|
166
|
+
_print([p])
|
|
167
|
+
return p
|
|
168
|
+
|