locust-cloud 1.12.4__py3-none-any.whl → 1.13.0__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 CHANGED
@@ -1,21 +1,29 @@
1
1
  import base64
2
2
  import gzip
3
+ import importlib.metadata
4
+ import json
3
5
  import logging
4
6
  import os
7
+ import pathlib
5
8
  import sys
6
9
  import threading
10
+ import time
7
11
  import tomllib
8
12
  import urllib.parse
13
+ import webbrowser
9
14
  from argparse import Namespace
10
15
  from collections import OrderedDict
16
+ from dataclasses import dataclass
11
17
  from typing import IO, Any
12
18
 
13
19
  import configargparse
20
+ import jwt
21
+ import platformdirs
14
22
  import requests
15
23
  import socketio
16
24
  import socketio.exceptions
17
- from locust_cloud import __version__
18
- from locust_cloud.credential_manager import CredentialError, CredentialManager
25
+
26
+ __version__ = importlib.metadata.version("locust-cloud")
19
27
 
20
28
 
21
29
  class LocustTomlConfigParser(configargparse.TomlConfigParser):
@@ -49,7 +57,7 @@ parser = configargparse.ArgumentParser(
49
57
  "cloud.conf",
50
58
  ],
51
59
  auto_env_var_prefix="LOCUSTCLOUD_",
52
- formatter_class=configargparse.RawDescriptionHelpFormatter,
60
+ formatter_class=configargparse.RawTextHelpFormatter,
53
61
  config_file_parser_class=configargparse.CompositeConfigParser(
54
62
  [
55
63
  LocustTomlConfigParser(["tool.locust"]),
@@ -108,36 +116,16 @@ advanced.add_argument(
108
116
  help="Optional requirements.txt file that contains your external libraries.",
109
117
  )
110
118
  advanced.add_argument(
111
- "--region",
112
- type=str,
113
- default=os.environ.get("AWS_DEFAULT_REGION"),
114
- help="Sets the AWS region to use for the deployed cluster, e.g. us-east-1. It defaults to use AWS_DEFAULT_REGION env var, like AWS tools.",
115
- )
116
- parser.add_argument(
117
- "--aws-access-key-id",
118
- type=str,
119
- help=configargparse.SUPPRESS,
120
- env_var="AWS_ACCESS_KEY_ID",
121
- default=None,
122
- )
123
- parser.add_argument(
124
- "--aws-secret-access-key",
125
- type=str,
126
- help=configargparse.SUPPRESS,
127
- env_var="AWS_SECRET_ACCESS_KEY",
128
- default=None,
129
- )
130
- parser.add_argument(
131
- "--username",
132
- type=str,
119
+ "--login",
120
+ action="store_true",
121
+ default=False,
133
122
  help=configargparse.SUPPRESS,
134
- default=os.getenv("LOCUST_CLOUD_USERNAME", None), # backwards compatitibility for dmdb
135
123
  )
136
- parser.add_argument(
137
- "--password",
138
- type=str,
139
- help=configargparse.SUPPRESS,
140
- default=os.getenv("LOCUST_CLOUD_PASSWORD", None), # backwards compatitibility for dmdb
124
+ advanced.add_argument(
125
+ "--non-interactive",
126
+ action="store_true",
127
+ default=False,
128
+ help="This can be set when, for example, running in a CI/CD environment to ensure no interactive steps while executing.\nRequires that LOCUSTCLOUD_USERNAME, LOCUSTCLOUD_PASSWORD and LOCUSTCLOUD_REGION environment variables are set.",
141
129
  )
142
130
  parser.add_argument(
143
131
  "--workers",
@@ -153,7 +141,7 @@ parser.add_argument(
153
141
  parser.add_argument(
154
142
  "--image-tag",
155
143
  type=str,
156
- default="latest",
144
+ default=None,
157
145
  help=configargparse.SUPPRESS, # overrides the locust-cloud docker image tag. for internal use
158
146
  )
159
147
  parser.add_argument(
@@ -169,6 +157,7 @@ parser.add_argument(
169
157
  )
170
158
 
171
159
  options, locust_options = parser.parse_known_args()
160
+
172
161
  options: Namespace
173
162
  locust_options: list
174
163
 
@@ -183,51 +172,222 @@ logging.getLogger("boto3").setLevel(logging.INFO)
183
172
  logging.getLogger("requests").setLevel(logging.INFO)
184
173
  logging.getLogger("urllib3").setLevel(logging.INFO)
185
174
 
175
+ cloud_conf_file = pathlib.Path(platformdirs.user_config_dir(appname="locust-cloud")) / "config"
176
+ valid_regions = ["us-east-1", "eu-north-1"]
186
177
 
187
- api_url = os.environ.get("LOCUSTCLOUD_DEPLOYER_URL", f"https://api.{options.region}.locust.cloud/1")
188
178
 
179
+ def get_api_url(region):
180
+ return os.environ.get("LOCUSTCLOUD_DEPLOYER_URL", f"https://api.{region}.locust.cloud/1")
189
181
 
190
- def main() -> None:
191
- if options.version:
192
- print(f"locust-cloud version {__version__}")
193
- sys.exit(0)
194
182
 
195
- if not options.region:
196
- logger.error(
197
- "Setting a region is required to use Locust Cloud. Please ensure the AWS_DEFAULT_REGION env variable or the --region flag is set."
198
- )
183
+ @dataclass
184
+ class CloudConfig:
185
+ id_token: str | None = None
186
+ refresh_token: str | None = None
187
+ refresh_token_expires: int = 0
188
+ region: str | None = None
189
+
190
+
191
+ def read_cloud_config() -> CloudConfig:
192
+ if cloud_conf_file.exists():
193
+ with open(cloud_conf_file) as f:
194
+ return CloudConfig(**json.load(f))
195
+
196
+ return CloudConfig()
197
+
198
+
199
+ def write_cloud_config(config: CloudConfig) -> None:
200
+ cloud_conf_file.parent.mkdir(parents=True, exist_ok=True)
201
+
202
+ with open(cloud_conf_file, "w") as f:
203
+ json.dump(config.__dict__, f)
204
+
205
+
206
+ def web_login() -> None:
207
+ print("Enter the number for the region to authenticate against")
208
+ print()
209
+ for i, valid_region in enumerate(valid_regions, start=1):
210
+ print(f" {i}. {valid_region}")
211
+ print()
212
+ choice = input("> ")
213
+ try:
214
+ region_index = int(choice) - 1
215
+ assert 0 <= region_index < len(valid_regions)
216
+ except (ValueError, AssertionError):
217
+ print(f"Not a valid choice: '{choice}'")
199
218
  sys.exit(1)
200
- if options.region:
201
- os.environ["AWS_DEFAULT_REGION"] = options.region
202
219
 
203
- if not ((options.username and options.password) or (options.aws_access_key_id and options.aws_secret_access_key)):
204
- logger.error(
205
- "Authentication is required to use Locust Cloud. Please ensure the LOCUSTCLOUD_USERNAME and LOCUSTCLOUD_PASSWORD environment variables are set."
206
- )
220
+ region = valid_regions[region_index]
221
+
222
+ try:
223
+ response = requests.post(f"{get_api_url(region)}/cli-auth")
224
+ response.raise_for_status()
225
+ response_data = response.json()
226
+ authentication_url = response_data["authentication_url"]
227
+ result_url = response_data["result_url"]
228
+ except Exception as e:
229
+ print("Something went wrong trying to authorize the locust-cloud CLI:", str(e))
207
230
  sys.exit(1)
231
+
232
+ message = f"""
233
+ Attempting to automatically open the SSO authorization page in your default browser.
234
+ If the browser does not open or you wish to use a different device to authorize this request, open the following URL:
235
+
236
+ {authentication_url}
237
+ """.strip()
238
+ print()
239
+ print(message)
240
+
241
+ webbrowser.open_new_tab(authentication_url)
242
+
243
+ while True: # Should there be some kind of timeout?
244
+ response = requests.get(result_url)
245
+
246
+ if not response.ok:
247
+ print("Oh no!")
248
+ print(response.text)
249
+ sys.exit(1)
250
+
251
+ data = response.json()
252
+
253
+ if data["state"] == "pending":
254
+ time.sleep(1)
255
+ continue
256
+ elif data["state"] == "failed":
257
+ print(f"\nFailed to authorize CLI: {data['reason']}")
258
+ sys.exit(1)
259
+ elif data["state"] == "authorized":
260
+ print("\nAuthorization succeded")
261
+ break
262
+ else:
263
+ print("\nGot unexpected response when authorizing CLI")
264
+ sys.exit(1)
265
+
266
+ config = CloudConfig(
267
+ id_token=data["id_token"],
268
+ refresh_token=data["refresh_token"],
269
+ refresh_token_expires=data["refresh_token_expires"],
270
+ region=region,
271
+ )
272
+ write_cloud_config(config)
273
+
274
+
275
+ class ApiSession(requests.Session):
276
+ def __init__(self) -> None:
277
+ super().__init__()
278
+
279
+ if options.non_interactive:
280
+ username = os.getenv("LOCUSTCLOUD_USERNAME")
281
+ password = os.getenv("LOCUSTCLOUD_PASSWORD")
282
+ region = os.getenv("LOCUSTCLOUD_REGION")
283
+
284
+ if not all([username, password, region]):
285
+ print(
286
+ "Running with --non-interaction requires that LOCUSTCLOUD_USERNAME, LOCUSTCLOUD_PASSWORD and LOCUSTCLOUD_REGION environment variables are set."
287
+ )
288
+ sys.exit(1)
289
+
290
+ if region not in valid_regions:
291
+ print("Environment variable LOCUSTCLOUD_REGION needs to be set to one of", ", ".join(valid_regions))
292
+ sys.exit(1)
293
+
294
+ self.__configure_for_region(region)
295
+ response = requests.post(
296
+ self.__login_url,
297
+ json={"username": username, "password": password},
298
+ headers={"X-Client-Version": __version__},
299
+ )
300
+ if not response.ok:
301
+ print(f"Authentication failed: {response.text}")
302
+ sys.exit(1)
303
+
304
+ self.__refresh_token = response.json()["refresh_token"]
305
+ id_token = response.json()["cognito_client_id_token"]
306
+
307
+ else:
308
+ config = read_cloud_config()
309
+
310
+ if config.refresh_token_expires < time.time() + 24 * 60 * 60:
311
+ message = "You need to authenticate before proceeding. Please run:\n locust-cloud --login"
312
+ print(message)
313
+ sys.exit(1)
314
+
315
+ assert config.region
316
+ self.__configure_for_region(config.region)
317
+ self.__refresh_token = config.refresh_token
318
+ id_token = config.id_token
319
+
320
+ assert id_token
321
+
322
+ decoded = jwt.decode(id_token, options={"verify_signature": False})
323
+ self.__expiry_time = decoded["exp"] - 60 # Refresh 1 minute before expiry
324
+ self.headers["Authorization"] = f"Bearer {id_token}"
325
+
326
+ self.__sub = decoded["sub"]
327
+ self.headers["X-Client-Version"] = __version__
328
+
329
+ def __configure_for_region(self, region: str) -> None:
330
+ self.__region = region
331
+ self.api_url = get_api_url(region)
332
+ self.__login_url = f"{self.api_url}/auth/login"
333
+
334
+ logger.debug(f"Lambda url: {self.api_url}")
335
+
336
+ def __ensure_valid_authorization_header(self) -> None:
337
+ if self.__expiry_time > time.time():
338
+ return
339
+
340
+ logger.info(f"Authenticating ({self.__region}, v{__version__})")
341
+
342
+ response = requests.post(
343
+ self.__login_url,
344
+ json={"user_sub_id": self.__sub, "refresh_token": self.__refresh_token},
345
+ headers={"X-Client-Version": __version__},
346
+ )
347
+
348
+ if not response.ok:
349
+ logger.error(f"Authentication failed: {response.text}")
350
+ sys.exit(1)
351
+
352
+ # TODO: Technically the /login endpoint can return a challenge for you
353
+ # to change your password. Don't know how we should handle that
354
+ # in the cli.
355
+
356
+ id_token = response.json()["cognito_client_id_token"]
357
+ decoded = jwt.decode(id_token, options={"verify_signature": False})
358
+ self.__expiry_time = decoded["exp"] - 60 # Refresh 1 minute before expiry
359
+ self.headers["Authorization"] = f"Bearer {id_token}"
360
+
361
+ if not options.non_interactive:
362
+ config = read_cloud_config()
363
+ config.id_token = id_token
364
+ write_cloud_config(config)
365
+
366
+ def request(self, method, url, *args, **kwargs) -> requests.Response:
367
+ self.__ensure_valid_authorization_header()
368
+ return super().request(method, f"{self.api_url}{url}", *args, **kwargs)
369
+
370
+
371
+ def main() -> None:
372
+ if options.version:
373
+ print(f"locust-cloud version {__version__}")
374
+ sys.exit(0)
208
375
  if not options.locustfile:
209
376
  logger.error("A locustfile is required to run a test.")
210
377
  sys.exit(1)
211
378
 
212
- try:
213
- logger.info(f"Authenticating ({options.region}, v{__version__})")
214
- logger.debug(f"Lambda url: {api_url}")
215
- credential_manager = CredentialManager(
216
- lambda_url=api_url,
217
- access_key=options.aws_access_key_id,
218
- secret_key=options.aws_secret_access_key,
219
- username=options.username,
220
- password=options.password,
221
- )
379
+ if options.login:
380
+ try:
381
+ web_login()
382
+ except KeyboardInterrupt:
383
+ pass
384
+ sys.exit()
222
385
 
223
- credentials = credential_manager.get_current_credentials()
224
- cognito_client_id_token = credentials["cognito_client_id_token"]
225
- aws_access_key_id = credentials.get("access_key")
226
- aws_secret_access_key = credentials.get("secret_key")
227
- aws_session_token = credentials.get("token", "")
386
+ session = ApiSession()
228
387
 
388
+ try:
229
389
  if options.delete:
230
- delete(credential_manager)
390
+ delete(session)
231
391
  return
232
392
 
233
393
  try:
@@ -249,47 +409,41 @@ def main() -> None:
249
409
 
250
410
  logger.info("Deploying load generators")
251
411
  locust_env_variables = [
252
- {"name": env_variable, "value": str(os.environ[env_variable])}
412
+ {"name": env_variable, "value": os.environ[env_variable]}
253
413
  for env_variable in os.environ
254
414
  if env_variable.startswith("LOCUST_")
255
- and not env_variable
256
- in [
415
+ and env_variable
416
+ not in [
257
417
  "LOCUST_LOCUSTFILE",
258
418
  "LOCUST_USERS",
259
419
  "LOCUST_WEB_HOST_DISPLAY_NAME",
260
420
  "LOCUST_SKIP_MONKEY_PATCH",
261
421
  ]
262
- and os.environ[env_variable]
263
422
  ]
264
- deploy_endpoint = f"{api_url}/deploy"
265
423
  payload = {
266
424
  "locust_args": [
267
425
  {"name": "LOCUST_USERS", "value": str(options.users)},
268
426
  {"name": "LOCUST_FLAGS", "value": " ".join(locust_options)},
269
- {"name": "LOCUSTCLOUD_DEPLOYER_URL", "value": api_url},
427
+ {"name": "LOCUSTCLOUD_DEPLOYER_URL", "value": session.api_url},
270
428
  {"name": "LOCUSTCLOUD_PROFILE", "value": options.profile},
271
429
  *locust_env_variables,
272
430
  ],
273
431
  "locustfile": {"filename": options.locustfile, "data": locustfile_data},
274
432
  "user_count": options.users,
275
- "image_tag": options.image_tag,
276
433
  "mock_server": options.mock_server,
277
434
  }
435
+
436
+ if options.image_tag is not None:
437
+ payload["image_tag"] = options.image_tag
438
+
278
439
  if options.workers is not None:
279
440
  payload["worker_count"] = options.workers
441
+
280
442
  if options.requirements:
281
443
  payload["requirements"] = {"filename": options.requirements, "data": requirements_data}
282
- headers = {
283
- "Authorization": f"Bearer {cognito_client_id_token}",
284
- "Content-Type": "application/json",
285
- "AWS_ACCESS_KEY_ID": aws_access_key_id,
286
- "AWS_SECRET_ACCESS_KEY": aws_secret_access_key,
287
- "AWS_SESSION_TOKEN": aws_session_token,
288
- "X-Client-Version": __version__,
289
- }
444
+
290
445
  try:
291
- # logger.info(payload) # might be useful when debugging sometimes
292
- response = requests.post(deploy_endpoint, json=payload, headers=headers)
446
+ response = session.post("/deploy", json=payload)
293
447
  except requests.exceptions.RequestException as e:
294
448
  logger.error(f"Failed to deploy the load generators: {e}")
295
449
  sys.exit(1)
@@ -304,10 +458,9 @@ def main() -> None:
304
458
  f"HTTP {response.status_code}/{response.reason} - Response: {response.text} - URL: {response.request.url}"
305
459
  )
306
460
  sys.exit(1)
307
- except CredentialError as ce:
308
- logger.error(f"Credential error: {ce}")
309
- sys.exit(1)
461
+
310
462
  except KeyboardInterrupt:
463
+ # TODO: This would potentially leave a deployment running, combine with try-catch below?
311
464
  logger.debug("Interrupted by user")
312
465
  sys.exit(0)
313
466
 
@@ -318,10 +471,10 @@ def main() -> None:
318
471
  shutdown_allowed.set()
319
472
  reconnect_aborted = threading.Event()
320
473
  connect_timeout = threading.Timer(2 * 60, reconnect_aborted.set)
474
+ sio = socketio.Client(handle_sigint=False)
321
475
 
322
476
  try:
323
477
  ws_connection_info = urllib.parse.urlparse(log_ws_url)
324
- sio = socketio.Client(handle_sigint=False)
325
478
 
326
479
  @sio.event
327
480
  def connect():
@@ -364,38 +517,23 @@ def main() -> None:
364
517
 
365
518
  except KeyboardInterrupt:
366
519
  logger.debug("Interrupted by user")
367
- delete(credential_manager)
520
+ delete(session)
368
521
  shutdown_allowed.wait(timeout=90)
369
522
  except Exception as e:
370
523
  logger.exception(e)
371
- delete(credential_manager)
524
+ delete(session)
372
525
  sys.exit(1)
373
526
  else:
374
- delete(credential_manager)
527
+ delete(session)
375
528
  finally:
376
529
  sio.shutdown()
377
530
 
378
531
 
379
- def delete(credential_manager):
532
+ def delete(session):
380
533
  try:
381
534
  logger.info("Tearing down Locust cloud...")
382
- credential_manager.refresh_credentials()
383
- refreshed_credentials = credential_manager.get_current_credentials()
384
-
385
- headers = {
386
- "AWS_ACCESS_KEY_ID": refreshed_credentials.get("access_key", ""),
387
- "AWS_SECRET_ACCESS_KEY": refreshed_credentials.get("secret_key", ""),
388
- "Authorization": f"Bearer {refreshed_credentials.get('cognito_client_id_token', '')}",
389
- "X-Client-Version": __version__,
390
- }
391
-
392
- token = refreshed_credentials.get("token")
393
- if token:
394
- headers["AWS_SESSION_TOKEN"] = token
395
-
396
- response = requests.delete(
397
- f"{api_url}/teardown",
398
- headers=headers,
535
+ response = session.delete(
536
+ "/teardown",
399
537
  )
400
538
 
401
539
  if response.status_code == 200:
@@ -407,7 +545,7 @@ def delete(credential_manager):
407
545
  except Exception as e:
408
546
  logger.error(f"Could not automatically tear down Locust Cloud: {e.__class__.__name__}:{e}")
409
547
 
410
- logger.info("Done! ✨")
548
+ logger.info("Done! ✨") # FIXME: Should probably not say it's done since at this point it could still be running
411
549
 
412
550
 
413
551
  if __name__ == "__main__":
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: locust-cloud
3
- Version: 1.12.4
3
+ Version: 1.13.0
4
4
  Summary: Locust Cloud
5
5
  Project-URL: Homepage, https://locust.cloud
6
6
  Requires-Python: >=3.11
7
7
  Requires-Dist: boto3==1.34.125
8
- Requires-Dist: gevent-websocket==0.10.1
9
- Requires-Dist: locust>=2.32.5.dev10
10
- Requires-Dist: psycopg[binary,pool]>=3.2.1
8
+ Requires-Dist: configargparse==1.7
9
+ Requires-Dist: platformdirs>=4.3.6
11
10
  Requires-Dist: pyjwt>=2.0
12
11
  Requires-Dist: python-socketio[client]==5.11.4
13
12
  Description-Content-Type: text/markdown
@@ -0,0 +1,5 @@
1
+ locust_cloud/cloud.py,sha256=r0lWoP1QiT1Klkrecsr6LyPD_l1GMW2BFGRCRVkLowM,18124
2
+ locust_cloud-1.13.0.dist-info/METADATA,sha256=STWxyZOw0qE003r-Pf-8RU3KPFK3yjH_NsSo5ffoHh0,496
3
+ locust_cloud-1.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
+ locust_cloud-1.13.0.dist-info/entry_points.txt,sha256=PGyAb4e3aTsGS3N3VGShDl6VzJaXy7QwsEgsLOC7V00,57
5
+ locust_cloud-1.13.0.dist-info/RECORD,,
locust_cloud/__init__.py DELETED
@@ -1,133 +0,0 @@
1
- import importlib.metadata
2
- import os
3
- import sys
4
-
5
- os.environ["LOCUST_SKIP_MONKEY_PATCH"] = "1"
6
-
7
- from locust_cloud.socket_logging import setup_socket_logging
8
-
9
- if os.environ.get("LOCUST_MODE_MASTER") == "1":
10
- major, minor, *rest = os.environ["LOCUSTCLOUD_CLIENT_VERSION"].split(".")
11
-
12
- if int(major) > 1 or int(major) == 1 and int(minor) >= 12:
13
- setup_socket_logging()
14
-
15
- __version__ = importlib.metadata.version("locust-cloud")
16
-
17
- import logging
18
-
19
- import configargparse
20
- import locust.env
21
- import psycopg
22
- from locust import events
23
- from locust.argument_parser import LocustArgumentParser
24
- from locust_cloud.auth import register_auth
25
- from locust_cloud.idle_exit import IdleExit
26
- from locust_cloud.timescale.exporter import Exporter
27
- from locust_cloud.timescale.query import register_query
28
- from psycopg.conninfo import make_conninfo
29
- from psycopg_pool import ConnectionPool
30
-
31
- logger = logging.getLogger(__name__)
32
-
33
-
34
- @events.init_command_line_parser.add_listener
35
- def add_arguments(parser: LocustArgumentParser):
36
- if not (os.environ.get("PGHOST")):
37
- parser.add_argument_group(
38
- "locust-cloud",
39
- "locust-cloud disabled, because PGHOST was not set - this is normal for local runs",
40
- )
41
- return
42
-
43
- try:
44
- REGION = os.environ["AWS_DEFAULT_REGION"]
45
- except KeyError:
46
- logger.fatal("Missing AWS_DEFAULT_REGION env var")
47
- sys.exit(1)
48
-
49
- os.environ["LOCUST_BUILD_PATH"] = os.path.join(os.path.dirname(__file__), "webui/dist")
50
- locust_cloud = parser.add_argument_group(
51
- "locust-cloud",
52
- "Arguments for use with Locust cloud",
53
- )
54
- # do not set
55
- # used for sending the run id from master to workers
56
- locust_cloud.add_argument(
57
- "--run-id",
58
- type=str,
59
- env_var="LOCUSTCLOUD_RUN_ID",
60
- help=configargparse.SUPPRESS,
61
- )
62
- locust_cloud.add_argument(
63
- "--allow-signup",
64
- env_var="LOCUSTCLOUD_ALLOW_SIGNUP",
65
- help=configargparse.SUPPRESS,
66
- default=False,
67
- action="store_true",
68
- )
69
- locust_cloud.add_argument(
70
- "--allow-forgot-password",
71
- env_var="LOCUSTCLOUD_FORGOT_PASSWORD",
72
- help=configargparse.SUPPRESS,
73
- default=False,
74
- action="store_true",
75
- )
76
- locust_cloud.add_argument(
77
- "--graph-viewer",
78
- env_var="LOCUSTCLOUD_GRAPH_VIEWER",
79
- help=configargparse.SUPPRESS,
80
- default=False,
81
- action="store_true",
82
- )
83
- locust_cloud.add_argument(
84
- "--deployer-url",
85
- type=str,
86
- env_var="LOCUSTCLOUD_DEPLOYER_URL",
87
- help=configargparse.SUPPRESS,
88
- default=f"https://api.{REGION}.locust.cloud/1",
89
- )
90
- locust_cloud.add_argument(
91
- "--profile",
92
- type=str,
93
- env_var="LOCUSTCLOUD_PROFILE",
94
- help=configargparse.SUPPRESS,
95
- default=None,
96
- )
97
-
98
-
99
- def set_autocommit(conn: psycopg.Connection):
100
- conn.autocommit = True
101
-
102
-
103
- @events.init.add_listener
104
- def on_locust_init(environment: locust.env.Environment, **_args):
105
- if not (os.environ.get("PGHOST")):
106
- return
107
-
108
- conninfo = make_conninfo(
109
- sslmode="require",
110
- )
111
- pool = ConnectionPool(
112
- conninfo,
113
- min_size=1,
114
- max_size=20,
115
- configure=set_autocommit,
116
- check=ConnectionPool.check_connection,
117
- )
118
- pool.wait(timeout=10)
119
-
120
- if not environment.parsed_options.graph_viewer:
121
- IdleExit(environment)
122
- Exporter(environment, pool)
123
-
124
- if environment.web_ui:
125
- environment.web_ui.template_args["locustVersion"] = locust.__version__
126
- environment.web_ui.template_args["locustCloudVersion"] = __version__
127
- environment.web_ui.template_args["webBasePath"] = environment.parsed_options.web_base_path
128
-
129
- if environment.parsed_options.graph_viewer:
130
- environment.web_ui.template_args["isGraphViewer"] = True
131
-
132
- register_auth(environment)
133
- register_query(environment, pool)