Skip to content

Commit ce8f4e5

Browse files
authored
Conform URLs (#281)
* Make any URLs build from env vars tolerant of path prefix, trailing/leading slashes * Add comment * Lint
1 parent 76f67b1 commit ce8f4e5

File tree

9 files changed

+33
-17
lines changed

9 files changed

+33
-17
lines changed

engine/apps/integrations/metadata/heartbeat/_heartbeat_text_creator.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from dataclasses import dataclass
2-
from urllib.parse import urljoin
32

43
from django.conf import settings
54

5+
from common.api_helpers.utils import create_engine_url
6+
67

78
@dataclass
89
class IntegrationHeartBeatText:
@@ -31,7 +32,7 @@ def _get_heartbeat_expired_title(self):
3132
return heartbeat_expired_title
3233

3334
def _get_heartbeat_expired_message(self):
34-
heartbeat_docs_url = urljoin(settings.DOCS_URL, "/#/integrations/heartbeat")
35+
heartbeat_docs_url = create_engine_url("/#/integrations/heartbeat", override_base=settings.DOCS_URL)
3536
heartbeat_expired_message = (
3637
f"Amixr was waiting for a heartbeat from {self.integration_verbal}. "
3738
f"Heartbeat is missing. That could happen because {self.integration_verbal} stopped or"

engine/apps/integrations/mixins/browsable_instruction_mixin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import json
2-
from urllib.parse import urljoin
32

43
from django.conf import settings
54
from django.http import HttpResponse
65
from django.template import loader
76

7+
from common.api_helpers.utils import create_engine_url
8+
89

910
class BrowsableInstructionMixin:
1011
def get(self, request, alert_receive_channel, *args, **kwargs):
1112
template = loader.get_template("integration_link.html")
1213
# TODO Create associative array for integrations
13-
base_integration_docs_url = urljoin(settings.DOCS_URL, "/#/integrations/")
14+
base_integration_docs_url = create_engine_url("/#/integrations/", override_base=settings.DOCS_URL)
1415
docs_url = f'{base_integration_docs_url}{request.get_full_path().split("/")[3]}'
1516
show_button = True
1617
if request.get_full_path().split("/")[3] == "amazon_sns":

engine/apps/integrations/views.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import json
22
import logging
3-
from urllib.parse import urljoin
43

54
from django.apps import apps
65
from django.conf import settings
@@ -28,6 +27,7 @@
2827
from apps.integrations.tasks import create_alert, create_alertmanager_alerts
2928
from apps.sendgridapp.parse import Parse
3029
from apps.sendgridapp.permissions import AllowOnlySendgrid
30+
from common.api_helpers.utils import create_engine_url
3131

3232
logger = logging.getLogger(__name__)
3333

@@ -76,7 +76,7 @@ def handle_message(self, message, payload):
7676
raw_request_data = message
7777
title = message.get("AlarmName", "Alert")
7878
else:
79-
docs_amazon_sns_url = urljoin(settings.DOCS_URL, "/#/integrations/amazon_sns")
79+
docs_amazon_sns_url = create_engine_url("/#/integrations/amazon_sns", override_base=settings.DOCS_URL)
8080
title = "Alert"
8181
message_text = (
8282
"Non-JSON payload received. Please make sure you publish monitoring Alarms to SNS,"
@@ -272,7 +272,7 @@ def post(self, request, alert_receive_channel, *args, **kwargs):
272272
class HeartBeatAPIView(AlertChannelDefiningMixin, APIView):
273273
def get(self, request, alert_receive_channel):
274274
template = loader.get_template("heartbeat_link.html")
275-
docs_url = urljoin(settings.DOCS_URL, "/#/integrations/heartbeat")
275+
docs_url = create_engine_url("/#/integrations/heartbeat", override_base=settings.DOCS_URL)
276276
return HttpResponse(
277277
template.render(
278278
{

engine/apps/oss_installation/cloud_heartbeat.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from rest_framework import status
99

1010
from apps.base.utils import live_settings
11+
from common.api_helpers.utils import create_engine_url
1112

1213
logger = logging.getLogger(__name__)
1314

@@ -23,7 +24,7 @@ def setup_heartbeat_integration(name=None):
2324
# don't specify a team in the data, so heartbeat integration will be created in the General.
2425
name = name or f"OnCall Cloud Heartbeat {settings.BASE_URL}"
2526
data = {"type": "formatted_webhook", "name": name}
26-
url = urljoin(settings.GRAFANA_CLOUD_ONCALL_API_URL, "api/v1/integrations/")
27+
url = create_engine_url("api/v1/integrations/", override_base=settings.GRAFANA_CLOUD_ONCALL_API_URL)
2728
try:
2829
headers = {"Authorization": api_token}
2930
r = requests.post(url=url, data=data, headers=headers, timeout=5)

engine/apps/oss_installation/models/cloud_connector.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from apps.base.utils import live_settings
88
from apps.oss_installation.models.cloud_user_identity import CloudUserIdentity
99
from apps.user_management.models import User
10+
from common.api_helpers.utils import create_engine_url
1011
from common.constants.role import Role
1112
from settings.base import GRAFANA_CLOUD_ONCALL_API_URL
1213

@@ -33,7 +34,7 @@ def sync_with_cloud(cls, token=None):
3334
logger.warning("Unable to sync with cloud. GRAFANA_CLOUD_ONCALL_TOKEN is not set")
3435
error_msg = "GRAFANA_CLOUD_ONCALL_TOKEN is not set"
3536
else:
36-
info_url = urljoin(GRAFANA_CLOUD_ONCALL_API_URL, "api/v1/info/")
37+
info_url = create_engine_url("api/v1/info/", override_base=GRAFANA_CLOUD_ONCALL_API_URL)
3738
try:
3839
r = requests.get(info_url, headers={"AUTHORIZATION": api_token}, timeout=5)
3940
if r.status_code == 200:
@@ -62,7 +63,7 @@ def sync_users_with_cloud(self) -> tuple[bool, str]:
6263

6364
existing_emails = list(User.objects.filter(role__in=(Role.ADMIN, Role.EDITOR)).values_list("email", flat=True))
6465
matching_users = []
65-
users_url = urljoin(GRAFANA_CLOUD_ONCALL_API_URL, "api/v1/users")
66+
users_url = create_engine_url("api/v1/users", override_base=GRAFANA_CLOUD_ONCALL_API_URL)
6667

6768
fetch_next_page = True
6869
users_fetched = True
@@ -115,7 +116,10 @@ def sync_user_with_cloud(self, user):
115116
logger.warning(f"Unable to sync_user_with cloud user_id {user.id}. GRAFANA_CLOUD_ONCALL_TOKEN is not set")
116117
error_msg = "GRAFANA_CLOUD_ONCALL_TOKEN is not set"
117118
else:
118-
url = urljoin(GRAFANA_CLOUD_ONCALL_API_URL, f"api/v1/users/?email={user.email}&roles=0&roles=1&short=true")
119+
url = create_engine_url(
120+
f"api/v1/users/?email={user.email}&roles=0&roles=1&short=true",
121+
override_base=GRAFANA_CLOUD_ONCALL_API_URL,
122+
)
119123
try:
120124
r = requests.get(url, headers={"AUTHORIZATION": api_token}, timeout=5)
121125
if r.status_code != 200:

engine/apps/telegram/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import Optional, Tuple, Union
2-
from urllib.parse import urljoin
32

43
from telegram import Bot, InlineKeyboardMarkup, Message, ParseMode
54
from telegram.error import InvalidToken, Unauthorized
@@ -10,6 +9,7 @@
109
from apps.telegram.models import TelegramMessage
1110
from apps.telegram.renderers.keyboard import TelegramKeyboardRenderer
1211
from apps.telegram.renderers.message import TelegramMessageRenderer
12+
from common.api_helpers.utils import create_engine_url
1313

1414

1515
class TelegramClient:
@@ -34,7 +34,7 @@ def is_chat_member(self, chat_id: Union[int, str]) -> bool:
3434
return False
3535

3636
def register_webhook(self, webhook_url: Optional[str] = None) -> None:
37-
webhook_url = webhook_url or urljoin(live_settings.TELEGRAM_WEBHOOK_HOST, "/telegram/")
37+
webhook_url = webhook_url or create_engine_url("/telegram/", override_base=live_settings.TELEGRAM_WEBHOOK_HOST)
3838

3939
if webhook_url is None:
4040
webhook_url = live_settings.TELEGRAM_WEBHOOK_URL

engine/apps/twilioapp/models/phone_call.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
from urllib.parse import urljoin
32

43
import requests
54
from django.apps import apps
@@ -14,6 +13,7 @@
1413
from apps.base.utils import live_settings
1514
from apps.twilioapp.constants import TwilioCallStatuses
1615
from apps.twilioapp.twilio_client import twilio_client
16+
from common.api_helpers.utils import create_engine_url
1717
from common.utils import clean_markup, escape_for_twilio_phone_call
1818

1919
logger = logging.getLogger(__name__)
@@ -158,7 +158,7 @@ def created_for_slack(self):
158158

159159
@classmethod
160160
def _make_cloud_call(cls, user, message_body):
161-
url = urljoin(settings.GRAFANA_CLOUD_ONCALL_API_URL, "api/v1/make_call")
161+
url = create_engine_url("api/v1/make_call", override_base=settings.GRAFANA_CLOUD_ONCALL_API_URL)
162162
auth = {"Authorization": live_settings.GRAFANA_CLOUD_ONCALL_TOKEN}
163163
data = {
164164
"email": user.email,

engine/apps/twilioapp/models/sms_message.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
from urllib.parse import urljoin
32

43
import requests
54
from django.apps import apps
@@ -13,6 +12,7 @@
1312
from apps.base.utils import live_settings
1413
from apps.twilioapp.constants import TwilioMessageStatuses
1514
from apps.twilioapp.twilio_client import twilio_client
15+
from common.api_helpers.utils import create_engine_url
1616
from common.utils import clean_markup
1717

1818
logger = logging.getLogger(__name__)
@@ -123,7 +123,7 @@ def created_for_slack(self):
123123

124124
@classmethod
125125
def _send_cloud_sms(cls, user, message_body):
126-
url = urljoin(settings.GRAFANA_CLOUD_ONCALL_API_URL, "api/v1/send_sms")
126+
url = create_engine_url("api/v1/send_sms", override_base=settings.GRAFANA_CLOUD_ONCALL_API_URL)
127127
auth = {"Authorization": live_settings.GRAFANA_CLOUD_ONCALL_TOKEN}
128128
data = {
129129
"email": user.email,

engine/common/api_helpers/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ def validate_ical_url(url):
5454
return None
5555

5656

57+
"""
58+
This utility function is for building a URL when we don't know if the base URL
59+
has been given a trailing / such as reading from environment variable or user
60+
input. If the base URL is coming from a validated model field urljoin can be used
61+
instead. Do not use this function to append query parameters since a / is added
62+
to the end of the base URL if there isn't one.
63+
"""
64+
65+
5766
def create_engine_url(path, override_base=None):
5867
base = settings.BASE_URL
5968
if override_base:

0 commit comments

Comments
 (0)