pyntcli 0.1.89__py3-none-any.whl → 0.1.91__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.
pyntcli/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.89"
1
+ __version__ = "0.1.91"
pyntcli/analytics/send.py CHANGED
@@ -39,6 +39,7 @@ def set_user_id(user_id):
39
39
  CLI_START = "cli_start"
40
40
  LOGIN_START = "cli_login_start"
41
41
  LOGIN_DONE = "cli_login_done"
42
+ DOCKER_NATIVE_FLAG = "docker_native_flag"
42
43
  CICD = "CI/CD"
43
44
  ERROR = "error"
44
45
  DOCKER_PLATFORM = "platform"
pyntcli/commands/burp.py CHANGED
@@ -19,6 +19,9 @@ from pyntcli.commands import util, sub_command
19
19
  from pyntcli.ui import report as cli_reporter
20
20
  from pyntcli.transport import pynt_requests
21
21
 
22
+ PYNT_CONTAINER_INTERNAL_PORT = "5001"
23
+ PYNT_CONTAINER_INTERNAL_PROXY_PORT = "6666"
24
+
22
25
  methods = [
23
26
  "get",
24
27
  "post",
@@ -62,7 +65,7 @@ def decode_request(item) -> str:
62
65
  raise e
63
66
 
64
67
 
65
- def replay_req(session, item, proxy_port):
68
+ def replay_req(item, proxy_port):
66
69
  url = item["url"]
67
70
  if not util.is_http_handler(url):
68
71
  return None
@@ -91,9 +94,7 @@ def replay_req(session, item, proxy_port):
91
94
  headers[key] = value
92
95
 
93
96
  body = decoded_req.split("\r\n\r\n")[1]
94
- ui_thread.print(ui_thread.PrinterText(f"{url}"))
95
- pynt_requests.request_from_xml(
96
- session=session,
97
+ resp = pynt_requests.request_from_xml(
97
98
  method=method,
98
99
  url=url,
99
100
  headers=headers,
@@ -103,16 +104,16 @@ def replay_req(session, item, proxy_port):
103
104
  "https": "127.0.0.1:{}".format(proxy_port),
104
105
  },
105
106
  )
107
+ ui_thread.print(ui_thread.PrinterText(resp))
106
108
 
107
109
 
108
110
  def run_burp_xml(doc, proxy_port):
109
111
  items = doc["items"]["item"]
110
112
  ui_thread.print(ui_thread.PrinterText("Creating traffic from xml file"))
111
- session = pynt_requests.get_new_session()
112
113
  if isinstance(items, dict):
113
- replay_req(session, item=items, proxy_port=proxy_port)
114
+ replay_req(item=items, proxy_port=proxy_port)
114
115
  else:
115
- [replay_req(session, i, proxy_port=proxy_port) for i in doc["items"]["item"]]
116
+ [replay_req(i, proxy_port=proxy_port) for i in doc["items"]["item"]]
116
117
 
117
118
 
118
119
  def parse_xml(xml_path):
@@ -232,10 +233,8 @@ class BurpCommand(sub_command.PyntSubCommand):
232
233
  def run_cmd(self, args: argparse.Namespace):
233
234
  container = pynt_container.get_container_with_arguments(
234
235
  args,
235
- pynt_container.PyntDockerPort(args.port, args.port, "--port"),
236
- pynt_container.PyntDockerPort(
237
- args.proxy_port, args.proxy_port, "--proxy-port"
238
- ),
236
+ pynt_container.PyntDockerPort(PYNT_CONTAINER_INTERNAL_PORT, args.port, "--port"),
237
+ pynt_container.PyntDockerPort(PYNT_CONTAINER_INTERNAL_PROXY_PORT, args.proxy_port, "--proxy-port"),
239
238
  )
240
239
 
241
240
  for host in args.captured_domains:
@@ -295,7 +294,8 @@ class BurpCommand(sub_command.PyntSubCommand):
295
294
  tag="proxy-latest",
296
295
  detach=True,
297
296
  base_container=container,
298
- )
297
+ use_native=args.use_docker_native)
298
+
299
299
  proxy_docker.run()
300
300
  ui_thread.print_generator(proxy_docker.stdout)
301
301
 
@@ -15,6 +15,9 @@ from pyntcli.commands import util, sub_command
15
15
  from pyntcli.ui import report as cli_reporter
16
16
  from pyntcli.transport import pynt_requests
17
17
 
18
+ PYNT_CONTAINER_INTERNAL_PORT = "5001"
19
+ PYNT_CONTAINER_INTERNAL_PROXY_PORT = "6666"
20
+
18
21
 
19
22
  def command_usage():
20
23
  return (
@@ -133,10 +136,8 @@ class CommandSubCommand(sub_command.PyntSubCommand):
133
136
  def run_cmd(self, args: argparse.Namespace):
134
137
  container = pynt_container.get_container_with_arguments(
135
138
  args,
136
- pynt_container.PyntDockerPort(args.port, args.port, "--port"),
137
- pynt_container.PyntDockerPort(
138
- args.proxy_port, args.proxy_port, "--proxy-port"
139
- ),
139
+ pynt_container.PyntDockerPort(PYNT_CONTAINER_INTERNAL_PORT, args.port, "--port"),
140
+ pynt_container.PyntDockerPort(PYNT_CONTAINER_INTERNAL_PROXY_PORT, args.proxy_port, "--proxy-port"),
140
141
  )
141
142
 
142
143
  if args.captured_domains:
@@ -169,7 +170,8 @@ class CommandSubCommand(sub_command.PyntSubCommand):
169
170
  tag="proxy-latest",
170
171
  detach=True,
171
172
  base_container=container,
172
- )
173
+ use_native=args.use_docker_native)
174
+
173
175
  proxy_docker.run()
174
176
  ui_thread.print_generator(proxy_docker.stdout)
175
177
 
pyntcli/commands/har.py CHANGED
@@ -10,6 +10,7 @@ from pyntcli.commands import sub_command, util
10
10
 
11
11
  PYNT_CONTAINER_INTERNAL_PORT = "5001"
12
12
 
13
+
13
14
  def har_usage():
14
15
  return (
15
16
  ui_thread.PrinterText("Integration with static har file testing")
@@ -88,7 +89,7 @@ class HarSubCommand(sub_command.PyntSubCommand):
88
89
  tag="har-latest",
89
90
  detach=True,
90
91
  base_container=container,
91
- )
92
+ use_native=args.use_docker_native)
92
93
 
93
94
  har_docker.run()
94
95
 
@@ -15,6 +15,9 @@ from pyntcli.commands import util, sub_command
15
15
  from pyntcli.ui import report as cli_reporter
16
16
  from pyntcli.transport import pynt_requests
17
17
 
18
+ PYNT_CONTAINER_INTERNAL_PORT = "5001"
19
+ PYNT_CONTAINER_INTERNAL_PROXY_PORT = "6666"
20
+
18
21
 
19
22
  def listen_usage():
20
23
  return (
@@ -105,8 +108,8 @@ class ListenSubCommand(sub_command.PyntSubCommand):
105
108
  def run_cmd(self, args: argparse.Namespace):
106
109
  container = pynt_container.get_container_with_arguments(
107
110
  args,
108
- pynt_container.PyntDockerPort(args.port, args.port, "--port"),
109
- pynt_container.PyntDockerPort(args.proxy_port, args.proxy_port, "--proxy-port"))
111
+ pynt_container.PyntDockerPort(PYNT_CONTAINER_INTERNAL_PORT, args.port, "--port"),
112
+ pynt_container.PyntDockerPort(PYNT_CONTAINER_INTERNAL_PROXY_PORT, args.proxy_port, "--proxy-port"))
110
113
 
111
114
  for host in args.captured_domains:
112
115
  container.docker_arguments += ["--host-targets", host]
@@ -10,6 +10,7 @@ from pyntcli.ui.progress import PyntProgress
10
10
 
11
11
  PYNT_CONTAINER_INTERNAL_PORT = "5001"
12
12
 
13
+
13
14
  def newman_usage():
14
15
  return (
15
16
  ui_thread.PrinterText(
@@ -61,6 +62,7 @@ class NewmanSubCommand(sub_command.PyntSubCommand):
61
62
  return newman_cmd
62
63
 
63
64
  def run_cmd(self, args: argparse.Namespace):
65
+
64
66
  port = util.find_open_port()
65
67
  container = pynt_container.get_container_with_arguments(
66
68
  args, pynt_container.PyntDockerPort(src=PYNT_CONTAINER_INTERNAL_PORT, dest=port, name="--port")
@@ -107,12 +109,11 @@ class NewmanSubCommand(sub_command.PyntSubCommand):
107
109
  container.mounts += m
108
110
  newman_docker = pynt_container.PyntContainer(
109
111
  image_name=pynt_container.PYNT_DOCKER_IMAGE,
110
- tag="latest",
112
+ tag="newman-latest",
111
113
  detach=True,
112
114
  base_container=container,
113
- )
115
+ use_native=args.use_docker_native)
114
116
  newman_docker.run()
115
-
116
117
  healthcheck = partial(
117
118
  util.wait_for_healthcheck, "http://localhost:{}".format(port)
118
119
  )
@@ -14,6 +14,8 @@ from pyntcli.pynt_docker import pynt_container
14
14
  from pyntcli.ui import ui_thread
15
15
  from pyntcli.transport import pynt_requests
16
16
 
17
+ PYNT_CONTAINER_INTERNAL_PORT = "5001"
18
+
17
19
 
18
20
  class PyntPostmanException(Exception):
19
21
  pass
@@ -93,12 +95,13 @@ class PostmanSubCommand(sub_command.PyntSubCommand):
93
95
  ui_thread.print("application-id is not supported in postman integration, use the collection variables to set application id.")
94
96
  args.application_id = ""
95
97
 
96
- container = pynt_container.get_container_with_arguments(args, pynt_container.PyntDockerPort("5001", args.port, name="--port"))
98
+ container = pynt_container.get_container_with_arguments(args, pynt_container.PyntDockerPort(PYNT_CONTAINER_INTERNAL_PORT, args.port, name="--port"))
97
99
 
98
100
  postman_docker = pynt_container.PyntContainer(image_name=pynt_container.PYNT_DOCKER_IMAGE,
99
101
  tag="postman-latest",
100
102
  detach=True,
101
- base_container=container)
103
+ base_container=container,
104
+ use_native=args.use_docker_native)
102
105
 
103
106
  postman_docker.run()
104
107
  ui_thread.print_generator(postman_docker.stdout)
@@ -51,6 +51,9 @@ python3 -m pip install --upgrade pyntcli""", ui_thread.PrinterText.WARNING))
51
51
  ui_thread.print(ui_thread.PrinterText("""Error: Unable to check if Pynt CLI version is up-to-date due to VPN/proxy. Run Pynt with --insecure to fix.""", ui_thread.PrinterText.WARNING))
52
52
  except HTTPError:
53
53
  ui_thread.print("""Unable to check if Pynt CLI version is up-to-date""")
54
+ except Exception as e:
55
+ ui_thread.print(ui_thread.PrinterText("""We could not check for updates.""", ui_thread.PrinterText.WARNING))
56
+ pass
54
57
 
55
58
 
56
59
  class PyntCommand:
pyntcli/commands/root.py CHANGED
@@ -75,6 +75,11 @@ class BaseCommand:
75
75
  action="store_true",
76
76
  help="use to get more detailed execution information",
77
77
  )
78
+ parser.add_argument(
79
+ "--use-docker-native",
80
+ action="store_true",
81
+ help="Use native docker instead of docker sdk"
82
+ )
78
83
 
79
84
  def get_subparser(self) -> argparse._SubParsersAction:
80
85
  if self.subparser is None:
pyntcli/commands/util.py CHANGED
@@ -41,6 +41,7 @@ def wait_for_healthcheck(address):
41
41
  while start + HEALTHCHECK_TIMEOUT > time.time():
42
42
  try:
43
43
  res = pynt_requests.get(address + "/healthcheck")
44
+
44
45
  logger.debug("Health check response: {}".format(res.status_code))
45
46
  if res.status_code == 418:
46
47
  return
pyntcli/main.py CHANGED
@@ -21,7 +21,7 @@ from pyntcli.store import CredStore
21
21
 
22
22
  def shutdown_cli():
23
23
  analytics.stop()
24
- pynt_container.PyntContainerRegistery.instance().stop_all_containers()
24
+ pynt_container.PyntContainerRegistry.instance().stop_all_containers()
25
25
  ui_thread.stop()
26
26
 
27
27
 
@@ -33,8 +33,11 @@ def signal_handler(signal_number, frame):
33
33
  exit(0)
34
34
 
35
35
 
36
- def assert_docker_availability():
37
- pynt_container.get_docker_type()
36
+ def get_docker_platform_name(use_docker_native: bool = False) -> str:
37
+ if use_docker_native:
38
+ return pynt_container.get_docker_type_native()
39
+
40
+ return pynt_container.get_docker_type()
38
41
 
39
42
 
40
43
  def print_header():
@@ -68,17 +71,25 @@ def main():
68
71
  if len(argv) == 1:
69
72
  pynt_cmd.root.usage()
70
73
  return
74
+
71
75
  if argv[1] == "logout":
72
76
  logout()
73
77
  return
74
- if [n for n in argv if "--verbose" in n]:
78
+
79
+ if "--verbose" in argv:
75
80
  ui_thread.VERBOSE = True
81
+
82
+ use_docker_native = True if "--use-docker-native" in argv else False
83
+ if use_docker_native:
84
+ analytics.emit(analytics.DOCKER_NATIVE_FLAG, {"message": "using docker native flag"})
85
+
76
86
  log.set_source(__version__)
77
87
  ui_thread.print_verbose("Logging in...")
78
88
  user_id = login.user_id()
79
89
  start_analytics(user_id)
80
90
  ui_thread.print_verbose("Asserting docker is properly installed")
81
- assert_docker_availability()
91
+ platform_name = get_docker_platform_name(use_docker_native)
92
+ ui_thread.print_verbose("Docker platform: {}".format(platform_name))
82
93
  signal.signal(signal.SIGINT, signal_handler)
83
94
  cli = pynt_cmd.PyntCommand()
84
95
  cli.run_cmd(cli.parse_args(argv[1:]))
@@ -1,7 +1,10 @@
1
+ import platform
2
+ import subprocess
1
3
  import docker
2
4
  from docker.errors import DockerException, APIError, ImageNotFound
3
5
  from docker.types import Mount
4
6
  import os
7
+ import json
5
8
  import argparse
6
9
  from typing import List
7
10
  import base64
@@ -24,6 +27,10 @@ class DockerNotAvailableException(Exception):
24
27
  pass
25
28
 
26
29
 
30
+ class DockerNativeUnavailableException(Exception):
31
+ pass
32
+
33
+
27
34
  class ImageUnavailableException(Exception):
28
35
  pass
29
36
 
@@ -34,6 +41,16 @@ class PortInUseException(Exception):
34
41
  super().__init__(self.message)
35
42
 
36
43
 
44
+ def get_docker_type_native():
45
+ try:
46
+ version_data = json.loads(subprocess.check_output(["docker", "version", "--format", "{{json .}}"], text=True))
47
+ platform = version_data.get("Client", {}).get("Platform", {})
48
+ analytics.deferred_emit(analytics.DOCKER_PLATFORM, platform)
49
+ return platform.get("Name", "")
50
+ except Exception:
51
+ raise DockerNotAvailableException()
52
+
53
+
37
54
  def get_docker_type():
38
55
  try:
39
56
  c = docker.from_env()
@@ -51,7 +68,7 @@ def get_docker_type():
51
68
  raise DockerNotAvailableException()
52
69
 
53
70
 
54
- class PyntBaseConatiner():
71
+ class PyntBaseContainer():
55
72
  def __init__(self, docker_type, docker_arguments, mounts, environment={}) -> None:
56
73
  self.docker_type = docker_type
57
74
  self.docker_arguments = docker_arguments
@@ -66,7 +83,7 @@ class PyntDockerPort:
66
83
  self.name = name
67
84
 
68
85
 
69
- def get_container_with_arguments(args: argparse.Namespace, *port_args: PyntDockerPort) -> PyntBaseConatiner:
86
+ def get_container_with_arguments(args: argparse.Namespace, *port_args: PyntDockerPort) -> PyntBaseContainer:
70
87
  docker_arguments = []
71
88
  ports = {}
72
89
  for p in port_args:
@@ -110,14 +127,14 @@ def get_container_with_arguments(args: argparse.Namespace, *port_args: PyntDocke
110
127
  docker_arguments.append("--verbose")
111
128
 
112
129
  creds_path = os.path.dirname(CredStore().file_location)
113
- mitm_cert_path = os.path.join(creds_path,"cert")
114
- os.makedirs(mitm_cert_path,exist_ok=True)
115
- mounts.append(create_mount(mitm_cert_path, "/root/.mitmproxy"))
130
+ mitm_cert_path = os.path.join(creds_path, "cert")
131
+ os.makedirs(mitm_cert_path, exist_ok=True)
132
+ mounts.append(create_mount(mitm_cert_path, "/root/.mitmproxy"))
116
133
 
117
134
  env = {PYNT_ID: CredStore().get_tokens(), "PYNT_SAAS_URL": PYNT_SAAS}
118
135
  if user_set_all_variables():
119
136
  add_env_variables(env)
120
- return PyntBaseConatiner(docker_type, docker_arguments, mounts, env)
137
+ return PyntBaseContainer(docker_type, docker_arguments, mounts, env)
121
138
 
122
139
 
123
140
  def _container_image_from_tag(tag: str) -> str:
@@ -137,46 +154,194 @@ def add_env_variables(env: dict):
137
154
  env["PYNT_PARAM2"] = base64.b64encode(PYNT_PARAM2.encode('utf-8'))
138
155
 
139
156
 
140
- class PyntContainer():
141
- def __init__(self, image_name, tag, detach, base_container: PyntBaseConatiner) -> None:
142
- self.docker_client: docker.DockerClient = None
143
- self.image = image_name if not os.environ.get("IMAGE") else os.environ.get("IMAGE")
144
- self.tag = tag if not os.environ.get("TAG") else os.environ.get("TAG")
145
- self.detach = detach
157
+ def value_from_environment_variable(key, fallback=""):
158
+ e = os.environ.get(key)
159
+
160
+ if e:
161
+ ui_thread.print_verbose(f"Using environment variable {key}={e}")
162
+ return e
163
+ if fallback != "":
164
+ ui_thread.print_verbose(f"Using variable {key}={fallback}")
165
+ return fallback
166
+
167
+
168
+ class PyntContainerNative:
169
+ def __init__(self, image_name, tag, base_container, is_detach=True):
170
+ self.image_name = value_from_environment_variable("IMAGE", image_name)
171
+ self.tag = value_from_environment_variable("TAG", tag)
172
+ self.is_detach = is_detach
173
+ self.mounts = base_container.mounts
174
+ self.env_vars = base_container.environment
175
+ self.base_container = base_container
176
+ self.container_name = ""
177
+ self.system = platform.system().lower()
178
+
146
179
  self.stdout = None
147
180
  self.running = False
148
- self.container_name = ""
149
- self.base_container = base_container
150
181
 
151
- def _create_docker_client(self):
152
- self.docker_client = docker.from_env()
153
- pat = os.environ.get("DOCKER_PASSWORD")
154
- username = os.environ.get("DOCKER_USERNAME")
155
- registry = os.environ.get("DOCKER_REGISTRY")
156
- if pat and username and registry:
157
- self.docker_client.login(username=username, password=pat, registry=registry)
182
+ def is_alive(self):
183
+ command = ["docker", "ps", "--filter", f"name={self.container_name}", "--filter", "status=running"]
184
+ result = subprocess.run(
185
+ command,
186
+ capture_output=True,
187
+ text=True
188
+ )
189
+
190
+ return len(result.stdout.splitlines()) > 1
191
+
192
+ def run(self):
193
+ self.running = True
194
+ self.kill_other_instances()
195
+
196
+ self.get_image()
197
+ args = self.base_container.docker_arguments if self.base_container.docker_arguments else None
198
+ docker_command = ["docker", "run"]
199
+
200
+ if self.is_detach:
201
+ docker_command.append("-d")
202
+
203
+ mounts = []
204
+ for mount in self.base_container.mounts:
205
+ mounts.extend(["-v", f"{mount['Source']}:{mount['Target']}"])
206
+
207
+ env_vars = []
208
+ for key, value in self.base_container.environment.items():
209
+ env_vars.extend(self.adapt_environment_variable_partial(key, value))
210
+
211
+ docker_type_options = []
212
+ for key, value in self.base_container.docker_type.get_arguments().items():
213
+ if key == "ports":
214
+ if isinstance(value, dict):
215
+ for s, d in value.items():
216
+ # --publish source:destination for each port
217
+ docker_type_options.extend([f"-p", f"{s}:{d}"])
218
+ else:
219
+ docker_type_options.extend([f"--{key}={value}"])
220
+
221
+ docker_command += mounts
222
+ docker_command += env_vars
223
+ docker_command += docker_type_options
224
+ docker_command += [f"{self.image_name}:{self.tag}"]
225
+ docker_command += args
226
+
227
+ command = self.adapt_run_command(docker_command)
228
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
229
+ stdout, stderr = process.communicate()
230
+
231
+ if stderr:
232
+ print(stderr)
158
233
 
159
- def _is_docker_image_up_to_date(self, image):
160
- return True
234
+ container_id = stdout.strip()
161
235
 
162
- def _handle_outdated_docker_image(self, image):
163
- return image
236
+ if process.returncode and process.returncode != 0:
237
+ raise DockerNativeUnavailableException(f"Unable to perform docker run command, return code: {process.returncode}")
238
+
239
+ logs_process = subprocess.Popen(
240
+ ['docker', 'logs', container_id],
241
+ stdout=subprocess.PIPE,
242
+ stderr=subprocess.PIPE,
243
+ text=True
244
+ )
245
+
246
+ logs_stdout, logs_stderr = logs_process.communicate()
247
+
248
+ self.stdout = logs_stdout
164
249
 
165
250
  def kill_other_instances(self):
166
- for c in self.docker_client.containers.list():
167
- if len(c.image.tags) and _container_image_from_tag(c.image.tags[0]) == self.image:
168
- c.kill()
251
+ try:
252
+ ui_thread.print_verbose("Killing other pynt containers if such exist")
253
+ command = ["docker", "ps", "-q", "-f", f"ancestor={self.image_name}:{self.tag}"]
254
+ containers_output = subprocess.check_output(command, text=True)
255
+ if not containers_output:
256
+ return
257
+
258
+ container_ids = containers_output.splitlines()
259
+ for container_id in container_ids:
260
+ command = ["docker", "kill", container_id]
261
+ subprocess.run(command)
262
+
263
+ except subprocess.CalledProcessError:
264
+ analytics.emit(analytics.ERROR, {"error": "Unable to kill other pynt containers"})
265
+ ui_thread.print(ui_thread.PrinterText("Error: Unable to kill other pynt containers", ui_thread.PrinterText.WARNING))
266
+
267
+ def pull_image(self):
268
+ try:
269
+ command = ["docker", "pull", f"{self.image_name}:{self.tag}"]
270
+ subprocess.run(command, capture_output=True, text=True)
271
+ except subprocess.CalledProcessError:
272
+ analytics.emit(analytics.ERROR, {"error": "Unable to pull image from ghcr"})
273
+ ui_thread.print(ui_thread.PrinterText("Error: Docker unable to pull latest Pynt image due to VPN/proxy. If using a mirror for Docker images, visit docs.pynt.io for help.", ui_thread.PrinterText.WARNING))
274
+ return None
275
+
276
+ def get_image(self):
277
+ try:
278
+ ui_thread.print(ui_thread.PrinterText("Pulling latest docker image", ui_thread.PrinterText.INFO))
279
+ command = ['docker', 'pull', f'{self.image_name}:{self.tag}']
280
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
281
+ stdout, stderr = process.communicate()
282
+
283
+ if process.returncode != 0:
284
+ raise ImageUnavailableException(f"Failed to pull image: {stderr.decode().strip()}")
285
+
286
+ command = ['docker', 'images', '-q', f'{self.image_name}']
287
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
288
+ stdout, stderr = process.communicate()
289
+
290
+ stdout = stdout.decode('utf-8')
291
+ stderr = stderr.decode('utf-8')
292
+
293
+ if stderr:
294
+ ui_thread.print(ui_thread.PrinterText(f"Error: {stderr}", ui_thread.PrinterText.WARNING))
295
+ raise ImageUnavailableException("Failed to pull image")
296
+
297
+ if process.returncode != 0:
298
+ raise ImageUnavailableException("Failed to pull image")
299
+
300
+ image_id = stdout.strip()
301
+ return image_id
302
+ except Exception as e:
303
+ raise ImageUnavailableException(f"An error occurred: {str(e)}")
169
304
 
170
305
  def stop(self):
171
306
  if not self.running:
172
307
  return
173
-
174
308
  self.kill_other_instances()
309
+ self.running = False
175
310
 
176
- self.docker_client.close()
177
- self.docker_client = None
311
+ def adapt_run_command(self, docker_command=[]):
312
+ if self.system == "windows":
313
+ return ' '.join(docker_command)
314
+ return docker_command
315
+
316
+ def adapt_environment_variable_partial(self, key, value):
317
+ if self.system == "windows":
318
+ return ["-e", f"{key}={json.dumps(value)}"]
319
+ return ["-e", f"{key}={value}"]
320
+
321
+
322
+ class PyntContainerSDK:
323
+ def __init__(self, image_name, tag, base_container, is_detach=True) -> None:
324
+ self.image_name = value_from_environment_variable("IMAGE", image_name)
325
+ self.tag = value_from_environment_variable("TAG", tag)
326
+ self.base_container = base_container
327
+ self.is_detach = is_detach
328
+
329
+ self.mounts = base_container.mounts
330
+ self.env_vars = base_container.environment
331
+
332
+ self.docker_client: docker.DockerClient = None
333
+ self.container_name = ""
334
+ self.stdout = None
178
335
  self.running = False
179
336
 
337
+ def _initialize(self):
338
+ self.docker_client = docker.from_env()
339
+ docker_password = value_from_environment_variable("DOCKER_PASSWORD")
340
+ docker_username = value_from_environment_variable("DOCKER_USERNAME")
341
+ docker_registry = value_from_environment_variable("DOCKER_REGISTRY")
342
+ if docker_password and docker_username and docker_registry:
343
+ self.docker_client.login(username=docker_username, password=docker_password, registry=docker_registry)
344
+
180
345
  def is_alive(self):
181
346
  if not self.docker_client or not self.container_name:
182
347
  return False
@@ -187,35 +352,15 @@ class PyntContainer():
187
352
 
188
353
  return l[0].status == "running"
189
354
 
190
- def pull_image(self):
191
- try:
192
- return self.docker_client.images.pull(self.image, tag=self.tag)
193
- except APIError as e:
194
- analytics.emit(analytics.ERROR, {"error": "Unable to pull image from ghcr: {}".format(e)})
195
- ui_thread.print(ui_thread.PrinterText("Error: Docker unable to pull latest Pynt image due to VPN/proxy. If using a mirror for Docker images, visit docs.pynt.io for help.",
196
- ui_thread.PrinterText.WARNING))
197
- return None
198
-
199
- def get_image(self):
200
- try:
201
- image = self.pull_image()
202
- if not image:
203
- ui_thread.print(ui_thread.PrinterText("Trying to get pynt local image", ui_thread.PrinterText.INFO))
204
- image = self.docker_client.images.get('{}:{}'.format(self.image, self.tag))
205
- return image
206
- except ImageNotFound:
207
- raise ImageUnavailableException()
208
-
209
355
  def run(self):
210
356
  if not self.docker_client:
211
- self._create_docker_client()
357
+ self._initialize()
212
358
 
213
359
  self.running = True
214
360
 
215
361
  ui_thread.print_verbose("Killing other pynt containers if such exist")
216
362
  self.kill_other_instances()
217
363
 
218
- ui_thread.print(ui_thread.PrinterText("Pulling latest docker", ui_thread.PrinterText.INFO))
219
364
  image = self.get_image()
220
365
  ui_thread.print(ui_thread.PrinterText("Docker pull done", ui_thread.PrinterText.INFO))
221
366
 
@@ -223,7 +368,7 @@ class PyntContainer():
223
368
 
224
369
  run_arguments = {
225
370
  "image": image,
226
- "detach": self.detach,
371
+ "detach": self.is_detach,
227
372
  "mounts": self.base_container.mounts,
228
373
  "environment": self.base_container.environment,
229
374
  "stream": True,
@@ -231,33 +376,103 @@ class PyntContainer():
231
376
  "command": args
232
377
  }
233
378
 
234
- run_arguments.update(self.base_container.docker_type.get_argumets())
379
+ run_arguments.update(self.base_container.docker_type.get_arguments())
235
380
 
236
381
  ui_thread.print_verbose("Running pynt docker with arguments:\n {}".format(" ".join(args)))
237
382
  c = self.docker_client.containers.run(**run_arguments)
238
383
  self.container_name = c.name
239
384
  self.stdout = c.logs(stream=True)
240
385
 
241
- PyntContainerRegistery.instance().register_container(self)
386
+ def kill_other_instances(self):
387
+ for c in self.docker_client.containers.list():
388
+ if len(c.image.tags) and _container_image_from_tag(c.image.tags[0]) == self.image_name:
389
+ c.kill()
390
+
391
+ def pull_image(self):
392
+ try:
393
+ return self.docker_client.images.pull(self.image_name, tag=self.tag)
394
+ except APIError as e:
395
+ analytics.emit(analytics.ERROR, {"error": "Unable to pull image from ghcr: {}".format(e)})
396
+ ui_thread.print(ui_thread.PrinterText("Error: Docker unable to pull latest Pynt image due to VPN/proxy. Visit docs.pynt.io for help using a mirror for Docker images.", ui_thread.PrinterText.WARNING))
397
+ return None
398
+
399
+ def get_image(self):
400
+ ui_thread.print(ui_thread.PrinterText("Pulling latest docker image", ui_thread.PrinterText.INFO))
401
+ try:
402
+ image = self.pull_image()
403
+ if not image:
404
+ ui_thread.print(ui_thread.PrinterText("Trying to get pynt local image", ui_thread.PrinterText.INFO))
405
+ image = self.docker_client.images.get(f"{self.image_name}:{self.tag}")
406
+ return image
407
+ except ImageNotFound:
408
+ raise ImageUnavailableException()
409
+
410
+ def stop(self):
411
+ if not self.running:
412
+ return
413
+ self.kill_other_instances()
414
+ self.docker_client.close()
415
+ self.docker_client = None
416
+ self.running = False
417
+
418
+
419
+ class PyntContainer:
420
+ def __init__(self, image_name, tag, detach, base_container: PyntBaseContainer, use_native=False) -> None:
421
+ self.use_native = use_native
422
+
423
+ if use_native:
424
+ self.client_implementation = PyntContainerNative(image_name=image_name, tag=tag, base_container=base_container, is_detach=detach)
425
+ else:
426
+ self.client_implementation = PyntContainerSDK(image_name=image_name, tag=tag, base_container=base_container, is_detach=detach)
427
+
428
+ self.image = image_name
429
+ self.tag = tag
430
+ self.detach = detach
431
+ self.container_name = ""
432
+ self.base_container = base_container
433
+ self.stdout = None
434
+
435
+ def kill_other_instances(self):
436
+ self.client_implementation.kill_other_instances()
437
+
438
+ def stop(self):
439
+ self.client_implementation.stop()
440
+
441
+ def is_alive(self):
442
+ return self.client_implementation.is_alive()
443
+
444
+ def pull_image(self):
445
+ return self.client_implementation.pull_image()
446
+
447
+ def get_image(self):
448
+ return self.client_implementation.get_image()
449
+
450
+ def run(self):
451
+ self.client_implementation.run()
452
+ self.stdout = self.client_implementation.stdout
453
+ PyntContainerRegistry.instance().register_container(self)
454
+
455
+ def running(self):
456
+ return self.client_implementation.running
242
457
 
243
458
 
244
- class PyntDockerDesktopContainer():
459
+ class PyntDockerDesktopContainer:
245
460
  def __init__(self, ports) -> None:
246
461
  self.ports = ports
247
462
 
248
- def get_argumets(self):
463
+ def get_arguments(self):
249
464
  return {"ports": self.ports} if self.ports else {}
250
465
 
251
466
 
252
- class PyntNativeContainer():
467
+ class PyntNativeContainer:
253
468
  def __init__(self, network) -> None:
254
469
  self.network = network
255
470
 
256
- def get_argumets(self):
471
+ def get_arguments(self):
257
472
  return {"network": self.network} if self.network else {}
258
473
 
259
474
 
260
- class PyntContainerRegistery():
475
+ class PyntContainerRegistry:
261
476
  _instance = None
262
477
 
263
478
  def __init__(self) -> None:
@@ -265,10 +480,10 @@ class PyntContainerRegistery():
265
480
 
266
481
  @staticmethod
267
482
  def instance():
268
- if not PyntContainerRegistery._instance:
269
- PyntContainerRegistery._instance = PyntContainerRegistery()
483
+ if not PyntContainerRegistry._instance:
484
+ PyntContainerRegistry._instance = PyntContainerRegistry()
270
485
 
271
- return PyntContainerRegistery._instance
486
+ return PyntContainerRegistry._instance
272
487
 
273
488
  def register_container(self, c: PyntContainer):
274
489
  self.containers.append(c)
pyntcli/store/store.py CHANGED
@@ -11,7 +11,7 @@ class Store():
11
11
  self.connector: StoreConnector = None
12
12
  self._file = None
13
13
  self._connector_type = connector_type
14
-
14
+
15
15
  def _get_file_data(self):
16
16
  if self.connector:
17
17
  return
@@ -59,5 +59,5 @@ class CredStore(Store):
59
59
 
60
60
  def get_tokens(self):
61
61
  all_tokens = self.get("token")
62
- token_to_json_string = '{"token":'+str(all_tokens).replace("\'", "\"")+"}"
62
+ token_to_json_string = '{"token":' + str(all_tokens).replace("\'", "\"") + "}"
63
63
  return token_to_json_string
@@ -62,16 +62,12 @@ def put(url, data=None, **kwargs):
62
62
  return requests.put(url, data=data, verify=verify, **kwargs)
63
63
 
64
64
 
65
- def get_new_session():
66
- s = requests.session()
67
- s.max_redirects = 500
68
-
69
- return s
70
-
71
-
72
- def request_from_xml(
73
- session: requests.Session, method, url, proxies=None, data=None, **kwargs
74
- ):
75
- return session.request(
76
- method=method, url=url, data=data, verify=False, proxies=proxies, **kwargs
77
- )
65
+ def request_from_xml(method, url, proxies=None, data=None, **kwargs):
66
+ try:
67
+ requests.request(
68
+ method=method, url=url, data=data, verify=False, proxies=proxies, **kwargs
69
+ )
70
+
71
+ return url
72
+ except requests.exceptions.TooManyRedirects as e:
73
+ return "Too many redirects for {}".format(url)
pyntcli/ui/ui_thread.py CHANGED
@@ -77,12 +77,14 @@ def gen_func_loop(gen):
77
77
  data = l
78
78
  if type(data) == bytes:
79
79
  data = l.decode("utf-8")
80
- if not isinstance(data, AnsiText) and data[-1] == "\n":
80
+ if not isinstance(data, AnsiText) and data and data[-1] == "\n":
81
81
  data = data[:-1]
82
82
  _print(data)
83
83
 
84
84
 
85
85
  def print_generator(gen):
86
+ if gen == None:
87
+ return
86
88
  t = Thread(target=gen_func_loop, args=(gen,), daemon=True)
87
89
  t.start()
88
90
 
@@ -163,7 +165,7 @@ class Printer():
163
165
  pass
164
166
 
165
167
  while not self.print_queue.empty():
166
- self.console.print(self.print_queue.get())
168
+ self.console.print(self.print_queue.get(), end="")
167
169
 
168
170
  def print(self, data):
169
171
  if isinstance(data, PrinterText):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyntcli
3
- Version: 0.1.89
3
+ Version: 0.1.91
4
4
  Summary: Command line utility to handle all of Pynt's different integrations
5
5
  Author-email: Pynt-io <support@pynt.io>
6
6
  Project-URL: Homepage, https://pynt.io
@@ -1,43 +1,43 @@
1
- pyntcli/__init__.py,sha256=PY93tkOmcPlWKkwp9BLM87U29v6noa-gTaI98NliUTA,23
2
- pyntcli/main.py,sha256=eJFpT-haLVQAYP71_ZHjNaA5iG2wvDXy8km84GU0Mwo,6009
1
+ pyntcli/__init__.py,sha256=TjC2Yv3b693i4p1zyYBITDCreXfOMw-2BLFwgHmy35U,23
2
+ pyntcli/main.py,sha256=ucC9d4wlhUpvA92OXRIZ55iMdFXEDiAOVvP_wtRu7zs,6432
3
3
  pyntcli/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- pyntcli/analytics/send.py,sha256=pJOyOWl3g_Vm9apKK3LzNVqsnC6zsWA1bCK3ZegbLpc,3637
4
+ pyntcli/analytics/send.py,sha256=9TRAEoPbv4rWOZfcNaGanrRJAFvNs39P-uKSl49GcQE,3679
5
5
  pyntcli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  pyntcli/auth/login.py,sha256=TljsRXbEkNI1YUrKm5mlTw4YiecYScYUsit8Z8vstss,5228
7
7
  pyntcli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- pyntcli/commands/burp.py,sha256=A56AD9Cx-JmzrsHyVHEj4OQzFaXYQRIi_svU8HtBl5Y,12240
9
- pyntcli/commands/command.py,sha256=0lCMxXIyj3vAd8NfeQ2Ik1WhV8RHq17cwwaQn2isRzQ,10411
10
- pyntcli/commands/har.py,sha256=pl-qPZE6yOlLrruZ2bGiDkEpEVkLmnxb68bRxgzdszg,4120
8
+ pyntcli/commands/burp.py,sha256=NfvMPlw8yISaDzvSuF96MXGVTPKRS7yR84TNDqFiblU,12274
9
+ pyntcli/commands/command.py,sha256=uFwcr1YcekbMv0gs4mLIzfFSfn3cyTkoowTBjjZSJbc,10540
10
+ pyntcli/commands/har.py,sha256=IZGd900hz0fFM3iZTG3NNXB2NiuAJGeuv-I49eXrf-o,4158
11
11
  pyntcli/commands/id_command.py,sha256=UBEgMIpm4vauTCsKyixltiGUolNg_OfHEJvJ_i5BpJY,943
12
- pyntcli/commands/listen.py,sha256=L8lpa8r-mZor9F_ad9L7KQAznljSEOgn_LPURm4e6dM,8789
13
- pyntcli/commands/newman.py,sha256=tXbb9hY0rO1-INvEiXia5-_TDhiA5QqdmgOKs_Jd6DY,5238
14
- pyntcli/commands/postman.py,sha256=wh0sgJTTkUDY5GFKxCKMrcE9OmSZk6nufltsIlJb6As,5013
15
- pyntcli/commands/pynt_cmd.py,sha256=KOl9guUtesO2JcMM5nPKKkjnK6F9HV4jHHcoUk4KVhw,2825
16
- pyntcli/commands/root.py,sha256=inffMhaMJykoRC0T2f7uF3f5BEdJw_c6Q1e2FTG4bfU,3511
12
+ pyntcli/commands/listen.py,sha256=0wY9itDQyxSeHQA4GCGF67Xkb4ym48uP17WCPJMcLBE,8910
13
+ pyntcli/commands/newman.py,sha256=ocg85L7HopXRYkhLc75QUtHH9ey48Z6Yxy1stgVW1aE,5283
14
+ pyntcli/commands/postman.py,sha256=T1qdFHXX_kTW9p8PPbY9P7ynK4UcGoG3FfEMrRetwco,5163
15
+ pyntcli/commands/pynt_cmd.py,sha256=X39hiDq6blGh2sGpwVeUXOgnDlhHruGkOoR5aUBzw_k,2982
16
+ pyntcli/commands/root.py,sha256=-2aAiOKc_yXAlymxGove6su1mcvYsk4kCNaEawDKwy8,3677
17
17
  pyntcli/commands/static_file_extensions.py,sha256=PZJb02BI-64tbU-j3rdCNsXzTh7gkIDGxGKbKNw3h5k,1995
18
18
  pyntcli/commands/sub_command.py,sha256=GF3-rE_qk2L4jGPFqHLm9SdGINmu3EakhjJTFyWjRms,374
19
- pyntcli/commands/util.py,sha256=spTI_3z-fd0q7o1htvl-mw9-yKbO2ZESDAL-AsgWCb0,3217
19
+ pyntcli/commands/util.py,sha256=4p4rzNqL8nUJJfj1uy_JOHsFBEvVT6-k-V9GbAqHVyM,3218
20
20
  pyntcli/log/__init__.py,sha256=cOGwOYzMoshEbZiiasBGkj6wF0SBu3Jdpl-AuakDesw,19
21
21
  pyntcli/log/log.py,sha256=cWCdWmUaAwePwdhYDcgNMEG9d9RM34sGahxBCYEdv2Y,1069
22
22
  pyntcli/pynt_docker/__init__.py,sha256=PQIOVxc7XXtMLfEX7ojgwf_Z3mmTllO3ZvzUZTPOxQY,30
23
23
  pyntcli/pynt_docker/container_utils.py,sha256=_Onn7loInzyJAG2-Uk6CGpsuRyelmUFHOvtJj4Uzi9A,175
24
- pyntcli/pynt_docker/pynt_container.py,sha256=Lz4WJlzJj3YeRQ6bb9D34oeLGnY1c0UDIugoHu-zBWA,9318
24
+ pyntcli/pynt_docker/pynt_container.py,sha256=sqWKi3HUIn3AR9HgDYQtofCkaEQiOT3hG1nYQHf8RD8,17500
25
25
  pyntcli/store/__init__.py,sha256=xuS9OB21F6B1sUx5XPGxz_6WpG6-KTMbuq50RrZS5OY,29
26
26
  pyntcli/store/json_connector.py,sha256=UGs3uORw3iyn0YJ8kzab-veEZToA6d-ByXYuqEleWsA,560
27
- pyntcli/store/store.py,sha256=9KwalOd1EA1VtYwr9oJgBsPgUYakX5uyif_sNXGQ614,1917
27
+ pyntcli/store/store.py,sha256=4YqmcHRltbbSWewKQoOZH8QdyMFs6i3L8o2bXY_YvSw,1909
28
28
  pyntcli/store/store_connector.py,sha256=w4LzcpRZesUZL1f63RmLlWEFRtJ6Y6rcS6PkkGtO4MA,357
29
29
  pyntcli/transport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- pyntcli/transport/pynt_requests.py,sha256=e-km_PonlrnQ2jwZHcCfNuv4laFWpaJjH-_f4Fge26M,1700
30
+ pyntcli/transport/pynt_requests.py,sha256=C7OPvcKkRTcxSYuyiWKE59KgA9sRX0d6fm1wnopAmPo,1719
31
31
  pyntcli/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  pyntcli/ui/progress.py,sha256=RrnO_jJNunoyupylakmWmHOEPw3lh99OHpKBzL6OBiE,1008
33
33
  pyntcli/ui/pynt_errors.py,sha256=00UprD4tFViREv7kuXGQ99PAKGTpXYixxi3Ndeoeiew,689
34
34
  pyntcli/ui/report.py,sha256=W-icPSZrGLOubXgam0LpOvHLl_aZg9Zx9qIkL8Ym5PE,1930
35
- pyntcli/ui/ui_thread.py,sha256=4YzpO5dDrWpbTovMdHvv9ZQdLFJamZEAKXjF9rIIIoQ,5039
35
+ pyntcli/ui/ui_thread.py,sha256=XUBgLpYQjVhrilU-ofw7VSXgTiwneSdTxm61EvC3x4Q,5091
36
36
  tests/conftest.py,sha256=gToq5K74GtgeGQXjFvXSzMaE6axBYxAzcFG5XJPOXjI,427
37
37
  tests/auth/test_login.py,sha256=KFlzWhXBAuwdi7GXf16gCB3ya94LQG2wjcSChE149rQ,3798
38
38
  tests/store/test_cred_store.py,sha256=_7-917EtNC9eKEumO2_lt-7KuDmCwOZFaowCm7DbA_A,254
39
- pyntcli-0.1.89.dist-info/METADATA,sha256=p-7mBPbyYjorg8Ksfw8-X64mbgRuaU4wcGkxcYL16No,463
40
- pyntcli-0.1.89.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
41
- pyntcli-0.1.89.dist-info/entry_points.txt,sha256=kcGmqAxXDttNk2EPRcqunc_LTVp61gzakz0v-GEE2SY,43
42
- pyntcli-0.1.89.dist-info/top_level.txt,sha256=u9MDStwVHB7UG8PUcODeWCul_NvzL2EzoLvSlgwLHFs,30
43
- pyntcli-0.1.89.dist-info/RECORD,,
39
+ pyntcli-0.1.91.dist-info/METADATA,sha256=4OkdycIkBOdwvZhqis_OTurQirR9lvVkutdeC95s_ls,463
40
+ pyntcli-0.1.91.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
41
+ pyntcli-0.1.91.dist-info/entry_points.txt,sha256=kcGmqAxXDttNk2EPRcqunc_LTVp61gzakz0v-GEE2SY,43
42
+ pyntcli-0.1.91.dist-info/top_level.txt,sha256=u9MDStwVHB7UG8PUcODeWCul_NvzL2EzoLvSlgwLHFs,30
43
+ pyntcli-0.1.91.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.1)
2
+ Generator: setuptools (71.0.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5