locust-cloud 1.14.0__py3-none-any.whl → 1.14.2__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.
- locust_cloud/cloud.py +71 -32
- {locust_cloud-1.14.0.dist-info → locust_cloud-1.14.2.dist-info}/METADATA +1 -1
- locust_cloud-1.14.2.dist-info/RECORD +5 -0
- locust_cloud-1.14.0.dist-info/RECORD +0 -5
- {locust_cloud-1.14.0.dist-info → locust_cloud-1.14.2.dist-info}/WHEEL +0 -0
- {locust_cloud-1.14.0.dist-info → locust_cloud-1.14.2.dist-info}/entry_points.txt +0 -0
locust_cloud/cloud.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import base64
|
2
2
|
import gzip
|
3
3
|
import importlib.metadata
|
4
|
+
import io
|
4
5
|
import json
|
5
6
|
import logging
|
6
7
|
import os
|
@@ -11,10 +12,12 @@ import time
|
|
11
12
|
import tomllib
|
12
13
|
import urllib.parse
|
13
14
|
import webbrowser
|
14
|
-
from argparse import Namespace
|
15
|
+
from argparse import ArgumentTypeError, Namespace
|
15
16
|
from collections import OrderedDict
|
17
|
+
from collections.abc import Generator
|
16
18
|
from dataclasses import dataclass
|
17
19
|
from typing import IO, Any
|
20
|
+
from zipfile import ZipFile
|
18
21
|
|
19
22
|
import configargparse
|
20
23
|
import jwt
|
@@ -24,6 +27,7 @@ import socketio
|
|
24
27
|
import socketio.exceptions
|
25
28
|
|
26
29
|
__version__ = importlib.metadata.version("locust-cloud")
|
30
|
+
CWD = pathlib.Path.cwd()
|
27
31
|
|
28
32
|
|
29
33
|
class LocustTomlConfigParser(configargparse.TomlConfigParser):
|
@@ -48,6 +52,51 @@ class LocustTomlConfigParser(configargparse.TomlConfigParser):
|
|
48
52
|
return result
|
49
53
|
|
50
54
|
|
55
|
+
def valid_extra_files_path(file_path: str) -> pathlib.Path:
|
56
|
+
p = pathlib.Path(file_path).resolve()
|
57
|
+
|
58
|
+
if not CWD in p.parents:
|
59
|
+
raise ArgumentTypeError(f"Can only reference files under current working directory: {CWD}")
|
60
|
+
if not p.exists():
|
61
|
+
raise ArgumentTypeError(f"File not found: {file_path}")
|
62
|
+
return p
|
63
|
+
|
64
|
+
|
65
|
+
def transfer_encode(file_name: str, stream: IO[bytes]) -> dict[str, str]:
|
66
|
+
return {
|
67
|
+
"filename": file_name,
|
68
|
+
"data": base64.b64encode(gzip.compress(stream.read())).decode(),
|
69
|
+
}
|
70
|
+
|
71
|
+
|
72
|
+
def transfer_encoded_file(file_path: str) -> dict[str, str]:
|
73
|
+
try:
|
74
|
+
with open(file_path, "rb") as f:
|
75
|
+
return transfer_encode(file_path, f)
|
76
|
+
except FileNotFoundError:
|
77
|
+
raise ArgumentTypeError(f"File not found: {file_path}")
|
78
|
+
|
79
|
+
|
80
|
+
def transfer_encoded_extra_files(paths: list[pathlib.Path]) -> dict[str, str]:
|
81
|
+
def expanded(paths: list[pathlib.Path]) -> Generator[pathlib.Path, None, None]:
|
82
|
+
for path in paths:
|
83
|
+
if path.is_dir():
|
84
|
+
for root, _, file_names in os.walk(path):
|
85
|
+
for file_name in file_names:
|
86
|
+
yield pathlib.Path(root) / file_name
|
87
|
+
else:
|
88
|
+
yield path
|
89
|
+
|
90
|
+
buffer = io.BytesIO()
|
91
|
+
|
92
|
+
with ZipFile(buffer, "w") as zf:
|
93
|
+
for path in set(expanded(paths)):
|
94
|
+
zf.write(path.relative_to(CWD))
|
95
|
+
|
96
|
+
buffer.seek(0)
|
97
|
+
return transfer_encode("extra-files.zip", buffer)
|
98
|
+
|
99
|
+
|
51
100
|
parser = configargparse.ArgumentParser(
|
52
101
|
default_config_files=[
|
53
102
|
"~/.locust.conf",
|
@@ -93,6 +142,7 @@ parser.add_argument(
|
|
93
142
|
default="locustfile.py",
|
94
143
|
help="The Python file that contains your test. Defaults to 'locustfile.py'.",
|
95
144
|
env_var="LOCUST_LOCUSTFILE",
|
145
|
+
type=transfer_encoded_file,
|
96
146
|
)
|
97
147
|
parser.add_argument(
|
98
148
|
"-u",
|
@@ -112,14 +162,14 @@ advanced.add_argument(
|
|
112
162
|
)
|
113
163
|
advanced.add_argument(
|
114
164
|
"--requirements",
|
115
|
-
type=
|
165
|
+
type=transfer_encoded_file,
|
116
166
|
help="Optional requirements.txt file that contains your external libraries.",
|
117
167
|
)
|
118
168
|
advanced.add_argument(
|
119
169
|
"--login",
|
120
170
|
action="store_true",
|
121
171
|
default=False,
|
122
|
-
help=
|
172
|
+
help="Launch an interactive session to authenticate your user.\nOnce completed your credentials will be stored and automatically refreshed for quite a long time.\nOnce those expires you will be prompted to perform another login.",
|
123
173
|
)
|
124
174
|
advanced.add_argument(
|
125
175
|
"--non-interactive",
|
@@ -155,11 +205,15 @@ parser.add_argument(
|
|
155
205
|
type=str,
|
156
206
|
help="Set a profile to group the testruns together",
|
157
207
|
)
|
208
|
+
parser.add_argument(
|
209
|
+
"--extra-files",
|
210
|
+
nargs="*",
|
211
|
+
type=valid_extra_files_path,
|
212
|
+
help="A list of extra files or directories to upload. Space-separated, e.g. --extra-files testdata.csv *.py my-directory/",
|
213
|
+
)
|
158
214
|
|
159
|
-
|
160
|
-
|
161
|
-
options: Namespace
|
162
|
-
locust_options: list
|
215
|
+
parsed_args: tuple[Namespace, list[str]] = parser.parse_known_args()
|
216
|
+
options, locust_options = parsed_args
|
163
217
|
|
164
218
|
logging.basicConfig(
|
165
219
|
format="[LOCUST-CLOUD] %(levelname)s: %(message)s",
|
@@ -281,7 +335,7 @@ class ApiSession(requests.Session):
|
|
281
335
|
|
282
336
|
if not all([username, password, region]):
|
283
337
|
print(
|
284
|
-
"Running with --non-
|
338
|
+
"Running with --non-interactive requires that LOCUSTCLOUD_USERNAME, LOCUSTCLOUD_PASSWORD and LOCUSTCLOUD_REGION environment variables are set."
|
285
339
|
)
|
286
340
|
sys.exit(1)
|
287
341
|
|
@@ -348,8 +402,9 @@ class ApiSession(requests.Session):
|
|
348
402
|
sys.exit(1)
|
349
403
|
|
350
404
|
# TODO: Technically the /login endpoint can return a challenge for you
|
351
|
-
# to change your password.
|
352
|
-
#
|
405
|
+
# to change your password.
|
406
|
+
# Now that we have a web based login flow we should force them to
|
407
|
+
# do a locust-cloud --login if we get that.
|
353
408
|
|
354
409
|
id_token = response.json()["cognito_client_id_token"]
|
355
410
|
decoded = jwt.decode(id_token, options={"verify_signature": False})
|
@@ -587,23 +642,6 @@ def main() -> None:
|
|
587
642
|
sys.exit()
|
588
643
|
|
589
644
|
try:
|
590
|
-
try:
|
591
|
-
with open(options.locustfile, "rb") as f:
|
592
|
-
locustfile_data = base64.b64encode(gzip.compress(f.read())).decode()
|
593
|
-
except FileNotFoundError:
|
594
|
-
logger.error(f"File not found: {options.locustfile}")
|
595
|
-
sys.exit(1)
|
596
|
-
|
597
|
-
requirements_data = None
|
598
|
-
|
599
|
-
if options.requirements:
|
600
|
-
try:
|
601
|
-
with open(options.requirements, "rb") as f:
|
602
|
-
requirements_data = base64.b64encode(gzip.compress(f.read())).decode()
|
603
|
-
except FileNotFoundError:
|
604
|
-
logger.error(f"File not found: {options.requirements}")
|
605
|
-
sys.exit(1)
|
606
|
-
|
607
645
|
logger.info("Deploying load generators")
|
608
646
|
locust_env_variables = [
|
609
647
|
{"name": env_variable, "value": os.environ[env_variable]}
|
@@ -625,7 +663,7 @@ def main() -> None:
|
|
625
663
|
{"name": "LOCUSTCLOUD_PROFILE", "value": options.profile},
|
626
664
|
*locust_env_variables,
|
627
665
|
],
|
628
|
-
"locustfile":
|
666
|
+
"locustfile": options.locustfile,
|
629
667
|
"user_count": options.users,
|
630
668
|
"mock_server": options.mock_server,
|
631
669
|
}
|
@@ -637,7 +675,10 @@ def main() -> None:
|
|
637
675
|
payload["worker_count"] = options.workers
|
638
676
|
|
639
677
|
if options.requirements:
|
640
|
-
payload["requirements"] =
|
678
|
+
payload["requirements"] = options.requirements
|
679
|
+
|
680
|
+
if options.extra_files:
|
681
|
+
payload["extra_files"] = transfer_encoded_extra_files(options.extra_files)
|
641
682
|
|
642
683
|
try:
|
643
684
|
response = session.post("/deploy", json=payload)
|
@@ -658,7 +699,7 @@ def main() -> None:
|
|
658
699
|
session_id = response.json()["session_id"]
|
659
700
|
logger.debug(f"Session ID is {session_id}")
|
660
701
|
|
661
|
-
logger.info("Waiting for
|
702
|
+
logger.info("Waiting for load generators to be ready...")
|
662
703
|
websocket.connect(
|
663
704
|
log_ws_url,
|
664
705
|
auth=session_id,
|
@@ -708,8 +749,6 @@ def delete(session):
|
|
708
749
|
except Exception as e:
|
709
750
|
logger.error(f"Could not automatically tear down Locust Cloud: {e.__class__.__name__}:{e}")
|
710
751
|
|
711
|
-
logger.info("Done! ✨") # FIXME: Should probably not say it's done since at this point it could still be running
|
712
|
-
|
713
752
|
|
714
753
|
if __name__ == "__main__":
|
715
754
|
main()
|
@@ -0,0 +1,5 @@
|
|
1
|
+
locust_cloud/cloud.py,sha256=ro6oWsd6SEjBHDUWH7vrnvo0wY1Ljz8zeiJwhlaIF-g,26873
|
2
|
+
locust_cloud-1.14.2.dist-info/METADATA,sha256=it51qVDFVV6aTB0b3bAuIvF9xfIVdx6Wk6UB31PLzhs,497
|
3
|
+
locust_cloud-1.14.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
4
|
+
locust_cloud-1.14.2.dist-info/entry_points.txt,sha256=PGyAb4e3aTsGS3N3VGShDl6VzJaXy7QwsEgsLOC7V00,57
|
5
|
+
locust_cloud-1.14.2.dist-info/RECORD,,
|
@@ -1,5 +0,0 @@
|
|
1
|
-
locust_cloud/cloud.py,sha256=H_bATzG3Hyw8BRw0HmgLKTl5kX4y9KQuKCVTtzSAdjA,25466
|
2
|
-
locust_cloud-1.14.0.dist-info/METADATA,sha256=mZOGdXwLoyzOO3TeABH2eKsnw6RTz2d5Av0POdyvaIc,497
|
3
|
-
locust_cloud-1.14.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
4
|
-
locust_cloud-1.14.0.dist-info/entry_points.txt,sha256=PGyAb4e3aTsGS3N3VGShDl6VzJaXy7QwsEgsLOC7V00,57
|
5
|
-
locust_cloud-1.14.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|