rpcsetup.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #!/usr/bin/python3
  2. import json # noqa
  3. import re
  4. from collections.abc import Mapping
  5. from dataclasses import dataclass
  6. from typing import Any
  7. import click
  8. """Convenience script for setting up silos in a dev environment.
  9. Prints devserver commands with the necessary environment variables.
  10. """
  11. @dataclass(frozen=True)
  12. class RegionConfig:
  13. number: int
  14. port: int
  15. api_token: str
  16. @property
  17. def name(self) -> str:
  18. return f"devregion{self.number}"
  19. @property
  20. def bind(self) -> str:
  21. return f"localhost:{self.port}"
  22. def get_env_repr(self) -> Mapping[str, Any]:
  23. return {
  24. "name": self.name,
  25. "id": self.number,
  26. "address": f"http://{self.bind}/",
  27. "category": "MULTI_TENANT",
  28. "api_token": self.api_token,
  29. }
  30. def format_env_vars(env_vars: Mapping[str, str]) -> str:
  31. def format_env_var(name: str, value: str) -> str:
  32. value = re.sub("([\"$'\\\\])", "\\\\\\1", value) # https://xkcd.com/1638/
  33. return f'{name}="{value}"'
  34. return " ".join(format_env_var(name, value) for (name, value) in env_vars.items())
  35. @click.command()
  36. @click.option(
  37. "--api-token",
  38. type=str,
  39. required=True,
  40. help=(
  41. """An API token to authorize RPC requests between servers.
  42. At the time this script is being written, the authentication system for RPCs
  43. hasn't been developed. An API token is needed to ensure that an incoming
  44. request hits a REST endpoint, but it doesn't need to have any particular
  45. permission scope.
  46. Because dev instances of the control and region silo will presumably share a
  47. single database, this script assumes that the same API token will work for both.
  48. """
  49. ),
  50. )
  51. @click.option("--region-count", type=int, default=2, help="Number of region silos")
  52. @click.option(
  53. "--control-port",
  54. type=int,
  55. default=8000,
  56. help=(
  57. """Port on which to bind the control silo.
  58. Region silos will be bound on ascending numbers after this one in steps of 10.
  59. """
  60. ),
  61. )
  62. def main(api_token: str, region_count: int, control_port: int) -> None:
  63. sender_credentials = {
  64. "is_allowed": True,
  65. "control_silo_api_token": api_token,
  66. "control_silo_address": f"http://localhost:{control_port}/",
  67. }
  68. regions = [
  69. RegionConfig(n, control_port + 10 * n, api_token) for n in range(1, region_count + 1)
  70. ]
  71. region_config = json.dumps([r.get_env_repr() for r in regions])
  72. common_env_vars = {}
  73. common_env_vars["SENTRY_REGION_CONFIG"] = region_config
  74. common_env_vars["SENTRY_DEV_HYBRID_CLOUD_RPC_SENDER"] = json.dumps(sender_credentials)
  75. control_env_vars = common_env_vars.copy()
  76. control_env_vars["SENTRY_SILO_MODE"] = "CONTROL"
  77. control_env_vars["SENTRY_DEVSERVER_BIND"] = f"localhost:{control_port}"
  78. print(f"# Control silo\n{format_env_vars(control_env_vars)} sentry devserver") # noqa
  79. for region in regions:
  80. region_env_vars = common_env_vars.copy()
  81. region_env_vars["SENTRY_SILO_MODE"] = "REGION"
  82. region_env_vars["SENTRY_REGION"] = region.name
  83. region_env_vars["SENTRY_DEVSERVER_BIND"] = region.bind
  84. print(f"\n# {region.name}\n{format_env_vars(region_env_vars)} sentry devserver") # noqa
  85. main()