pulumi-django-azure 1.0.25__py3-none-any.whl → 1.0.27__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.
Potentially problematic release.
This version of pulumi-django-azure might be problematic. Click here for more details.
- pulumi_django_azure/azure_helper.py +51 -4
- pulumi_django_azure/django_deployment.py +34 -34
- pulumi_django_azure/management/commands/purge_cache.py +15 -0
- pulumi_django_azure/middleware.py +31 -8
- pulumi_django_azure/settings.py +18 -4
- {pulumi_django_azure-1.0.25.dist-info → pulumi_django_azure-1.0.27.dist-info}/METADATA +9 -33
- pulumi_django_azure-1.0.27.dist-info/RECORD +12 -0
- {pulumi_django_azure-1.0.25.dist-info → pulumi_django_azure-1.0.27.dist-info}/WHEEL +1 -1
- pulumi_django_azure-1.0.25.dist-info/RECORD +0 -12
- pulumi_django_azure-1.0.25.dist-info/licenses/LICENSE +0 -21
- {pulumi_django_azure-1.0.25.dist-info → pulumi_django_azure-1.0.27.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import json
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
5
|
+
import time
|
|
4
6
|
from dataclasses import dataclass
|
|
5
7
|
from subprocess import check_output
|
|
6
8
|
|
|
@@ -8,11 +10,17 @@ from azure.identity import DefaultAzureCredential
|
|
|
8
10
|
from azure.mgmt.resource import SubscriptionClient
|
|
9
11
|
from azure.mgmt.resource.subscriptions.models import Subscription
|
|
10
12
|
|
|
13
|
+
_redis_token_cache = None
|
|
14
|
+
_database_token_cache = None
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("pulumi_django_azure.azure_helper")
|
|
17
|
+
|
|
18
|
+
|
|
11
19
|
# Azure credentials
|
|
12
20
|
AZURE_CREDENTIAL = DefaultAzureCredential()
|
|
13
21
|
|
|
14
22
|
# Get the local IP addresses of the machine (only when runnig on Azure)
|
|
15
|
-
if os.environ.get("
|
|
23
|
+
if os.environ.get("IS_AZURE_ENVIRONMENT"):
|
|
16
24
|
LOCAL_IP_ADDRESSES = check_output(["hostname", "--all-ip-addresses"]).decode("utf-8").strip().split(" ")
|
|
17
25
|
else:
|
|
18
26
|
LOCAL_IP_ADDRESSES = []
|
|
@@ -22,7 +30,26 @@ def get_db_password() -> str:
|
|
|
22
30
|
"""
|
|
23
31
|
Get a valid password for the database.
|
|
24
32
|
"""
|
|
25
|
-
|
|
33
|
+
global _database_token_cache
|
|
34
|
+
_database_token_cache = AZURE_CREDENTIAL.get_token("https://ossrdbms-aad.database.windows.net/.default")
|
|
35
|
+
|
|
36
|
+
logger.debug("New database token: %s", _database_token_cache)
|
|
37
|
+
|
|
38
|
+
return _database_token_cache.token
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def db_token_will_expire(treshold=300) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Check if the database token will expire in the next treshold seconds.
|
|
44
|
+
"""
|
|
45
|
+
# If the token is not cached, we consider it expired (so a new one will be fetched)
|
|
46
|
+
if _database_token_cache is None:
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
logger.debug("Database token expires on: %s", _database_token_cache.expires_on)
|
|
50
|
+
|
|
51
|
+
# If the token is cached, check if it will expire in the next treshold seconds
|
|
52
|
+
return _database_token_cache.expires_on - time.time() < treshold
|
|
26
53
|
|
|
27
54
|
|
|
28
55
|
@dataclass
|
|
@@ -35,8 +62,28 @@ def get_redis_credentials() -> RedisCredentials:
|
|
|
35
62
|
"""
|
|
36
63
|
Get valid credentials for the Redis cache.
|
|
37
64
|
"""
|
|
38
|
-
|
|
39
|
-
|
|
65
|
+
global _redis_token_cache
|
|
66
|
+
_redis_token_cache = AZURE_CREDENTIAL.get_token("https://redis.azure.com/.default")
|
|
67
|
+
|
|
68
|
+
t = _redis_token_cache.token
|
|
69
|
+
|
|
70
|
+
logger.debug("New Redis token: %s", _redis_token_cache)
|
|
71
|
+
|
|
72
|
+
return RedisCredentials(_extract_username_from_token(t), t)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def redis_token_will_expire(treshold=300) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Check if the Redis token will expire in the next treshold seconds.
|
|
78
|
+
"""
|
|
79
|
+
# If the token is not cached, we consider it expired (so a new one will be fetched)
|
|
80
|
+
if _redis_token_cache is None:
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
logger.debug("Redis token expires on: %s", _redis_token_cache.expires_on)
|
|
84
|
+
|
|
85
|
+
# If the token is cached, check if it will expire in the next treshold seconds
|
|
86
|
+
return _redis_token_cache.expires_on - time.time() < treshold
|
|
40
87
|
|
|
41
88
|
|
|
42
89
|
def get_subscription() -> Subscription:
|
|
@@ -14,7 +14,7 @@ class HostDefinition:
|
|
|
14
14
|
:param identifier: An identifier for this host definition (optional).
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
def __init__(self, host: str, zone: azure.
|
|
17
|
+
def __init__(self, host: str, zone: azure.dns.Zone | None = None, identifier: str | None = None):
|
|
18
18
|
self.host = host
|
|
19
19
|
self.zone = zone
|
|
20
20
|
self._identifier = identifier
|
|
@@ -69,9 +69,9 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
69
69
|
storage_account_name: str,
|
|
70
70
|
storage_allowed_origins: Sequence[str] | None = None,
|
|
71
71
|
pgadmin_access_ip: Sequence[str] | None = None,
|
|
72
|
-
pgadmin_dns_zone: azure.
|
|
72
|
+
pgadmin_dns_zone: azure.dns.Zone | None = None,
|
|
73
73
|
cache_ip_prefix: str | None = None,
|
|
74
|
-
cache_sku: azure.
|
|
74
|
+
cache_sku: azure.redis.SkuArgs | None = None,
|
|
75
75
|
cdn_host: HostDefinition | None = None,
|
|
76
76
|
opts=None,
|
|
77
77
|
):
|
|
@@ -220,14 +220,14 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
220
220
|
if custom_host:
|
|
221
221
|
if custom_host.zone:
|
|
222
222
|
# Create a DNS record for the custom host in the given zone
|
|
223
|
-
rs = azure.
|
|
223
|
+
rs = azure.dns.RecordSet(
|
|
224
224
|
f"cdn-cname-{self._name}",
|
|
225
225
|
resource_group_name=self._rg,
|
|
226
226
|
zone_name=custom_host.zone.name,
|
|
227
227
|
relative_record_set_name=custom_host.host,
|
|
228
228
|
record_type="CNAME",
|
|
229
229
|
ttl=3600,
|
|
230
|
-
target_resource=azure.
|
|
230
|
+
target_resource=azure.dns.SubResourceArgs(
|
|
231
231
|
id=self._cdn_endpoint.id,
|
|
232
232
|
),
|
|
233
233
|
)
|
|
@@ -266,7 +266,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
266
266
|
)
|
|
267
267
|
|
|
268
268
|
# Create private DNS zone
|
|
269
|
-
dns = azure.
|
|
269
|
+
dns = azure.privatedns.PrivateZone(
|
|
270
270
|
f"dns-pgsql-{self._name}",
|
|
271
271
|
resource_group_name=self._rg,
|
|
272
272
|
location="global",
|
|
@@ -275,7 +275,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
275
275
|
)
|
|
276
276
|
|
|
277
277
|
# Link the private DNS zone to the VNet in order to make resolving work
|
|
278
|
-
azure.
|
|
278
|
+
azure.privatedns.VirtualNetworkLink(
|
|
279
279
|
f"vnet-link-pgsql-{self._name}",
|
|
280
280
|
resource_group_name=self._rg,
|
|
281
281
|
location="global",
|
|
@@ -308,23 +308,23 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
308
308
|
|
|
309
309
|
pulumi.export("pgsql_host", self._pgsql.fully_qualified_domain_name)
|
|
310
310
|
|
|
311
|
-
def _create_cache(self, sku: azure.
|
|
311
|
+
def _create_cache(self, sku: azure.redis.SkuArgs, ip_prefix: str):
|
|
312
312
|
# Create a Redis cache
|
|
313
|
-
self._cache = azure.
|
|
313
|
+
self._cache = azure.redis.Redis(
|
|
314
314
|
f"cache-{self._name}",
|
|
315
315
|
resource_group_name=self._rg,
|
|
316
316
|
sku=sku,
|
|
317
317
|
enable_non_ssl_port=False,
|
|
318
|
-
public_network_access=azure.
|
|
318
|
+
public_network_access=azure.redis.PublicNetworkAccess.DISABLED,
|
|
319
319
|
)
|
|
320
320
|
|
|
321
321
|
# Create an access policy that gives us access to the cache
|
|
322
|
-
self._cache_access_policy = azure.
|
|
322
|
+
self._cache_access_policy = azure.redis.AccessPolicy(
|
|
323
323
|
f"cache-access-policy-{self._name}",
|
|
324
324
|
resource_group_name=self._rg,
|
|
325
325
|
cache_name=self._cache.name,
|
|
326
|
-
# Same as the built in Data Contributor policy
|
|
327
|
-
permissions="+@all -@dangerous +cluster|info +cluster|nodes +cluster|slots allkeys",
|
|
326
|
+
# Same as the built in Data Contributor policy + flushdb permissions
|
|
327
|
+
permissions="+@all -@dangerous +flushdb +cluster|info +cluster|nodes +cluster|slots allkeys",
|
|
328
328
|
)
|
|
329
329
|
|
|
330
330
|
# Allocate a subnet for the cache
|
|
@@ -334,7 +334,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
334
334
|
)
|
|
335
335
|
|
|
336
336
|
# Create a private DNS zone for the cache
|
|
337
|
-
dns = azure.
|
|
337
|
+
dns = azure.privatedns.PrivateZone(
|
|
338
338
|
f"dns-cache-{self._name}",
|
|
339
339
|
resource_group_name=self._rg,
|
|
340
340
|
location="global",
|
|
@@ -342,7 +342,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
342
342
|
)
|
|
343
343
|
|
|
344
344
|
# Link the private DNS zone to the VNet in order to make resolving work
|
|
345
|
-
azure.
|
|
345
|
+
azure.privatedns.VirtualNetworkLink(
|
|
346
346
|
f"vnet-link-cache-{self._name}",
|
|
347
347
|
resource_group_name=self._rg,
|
|
348
348
|
location="global",
|
|
@@ -377,14 +377,14 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
377
377
|
)
|
|
378
378
|
|
|
379
379
|
# Create a DNS record for the cache
|
|
380
|
-
azure.
|
|
380
|
+
azure.privatedns.PrivateRecordSet(
|
|
381
381
|
f"dns-a-cache-{self._name}",
|
|
382
382
|
resource_group_name=self._rg,
|
|
383
383
|
private_zone_name=dns.name,
|
|
384
384
|
relative_record_set_name=self._cache.name,
|
|
385
385
|
record_type="A",
|
|
386
386
|
ttl=300,
|
|
387
|
-
a_records=[azure.
|
|
387
|
+
a_records=[azure.privatedns.ARecordArgs(ipv4_address=ip)],
|
|
388
388
|
)
|
|
389
389
|
|
|
390
390
|
def _create_subnet(
|
|
@@ -432,7 +432,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
432
432
|
sku=sku,
|
|
433
433
|
)
|
|
434
434
|
|
|
435
|
-
def _create_pgadmin_app(self, access_ip: Sequence[str] | None = None, dns_zone: azure.
|
|
435
|
+
def _create_pgadmin_app(self, access_ip: Sequence[str] | None = None, dns_zone: azure.dns.Zone | None = None):
|
|
436
436
|
# Determine the IP restrictions
|
|
437
437
|
ip_restrictions = []
|
|
438
438
|
default_restriction = azure.web.DefaultAction.ALLOW
|
|
@@ -490,20 +490,20 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
490
490
|
|
|
491
491
|
if dns_zone:
|
|
492
492
|
# Create a DNS record for the pgAdmin app
|
|
493
|
-
cname = azure.
|
|
493
|
+
cname = azure.dns.RecordSet(
|
|
494
494
|
f"dns-cname-pgadmin-{self._name}",
|
|
495
495
|
resource_group_name=self._rg,
|
|
496
496
|
zone_name=dns_zone.name,
|
|
497
497
|
relative_record_set_name="pgadmin",
|
|
498
498
|
record_type="CNAME",
|
|
499
499
|
ttl=3600,
|
|
500
|
-
cname_record=azure.
|
|
500
|
+
cname_record=azure.dns.CnameRecordArgs(
|
|
501
501
|
cname=app.default_host_name,
|
|
502
502
|
),
|
|
503
503
|
)
|
|
504
504
|
|
|
505
505
|
# For the certificate validation to work
|
|
506
|
-
txt_validation = azure.
|
|
506
|
+
txt_validation = azure.dns.RecordSet(
|
|
507
507
|
f"dns-txt-pgadmin-{self._name}",
|
|
508
508
|
resource_group_name=self._rg,
|
|
509
509
|
zone_name=dns_zone.name,
|
|
@@ -511,7 +511,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
511
511
|
record_type="TXT",
|
|
512
512
|
ttl=3600,
|
|
513
513
|
txt_records=[
|
|
514
|
-
azure.
|
|
514
|
+
azure.dns.TxtRecordArgs(
|
|
515
515
|
value=[app.custom_domain_verification_id],
|
|
516
516
|
)
|
|
517
517
|
],
|
|
@@ -631,11 +631,11 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
631
631
|
|
|
632
632
|
existing_binding.apply(_create_binding_with_cert)
|
|
633
633
|
|
|
634
|
-
def _create_comms_dns_records(self, suffix, host: HostDefinition, records: dict) -> list[azure.
|
|
634
|
+
def _create_comms_dns_records(self, suffix, host: HostDefinition, records: dict) -> list[azure.dns.RecordSet]:
|
|
635
635
|
created_records = []
|
|
636
636
|
|
|
637
637
|
# Domain validation and SPF record (one TXT record with multiple values)
|
|
638
|
-
r = azure.
|
|
638
|
+
r = azure.dns.RecordSet(
|
|
639
639
|
f"dns-comms-{suffix}-{host.identifier}-domain",
|
|
640
640
|
resource_group_name=self._rg,
|
|
641
641
|
zone_name=host.zone.name,
|
|
@@ -643,8 +643,8 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
643
643
|
record_type="TXT",
|
|
644
644
|
ttl=3600,
|
|
645
645
|
txt_records=[
|
|
646
|
-
azure.
|
|
647
|
-
azure.
|
|
646
|
+
azure.dns.TxtRecordArgs(value=[records["domain"]["value"]]),
|
|
647
|
+
azure.dns.TxtRecordArgs(value=[records["s_pf"]["value"]]),
|
|
648
648
|
],
|
|
649
649
|
)
|
|
650
650
|
created_records.append(r)
|
|
@@ -656,14 +656,14 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
656
656
|
else:
|
|
657
657
|
relative_record_set_name = f"{records[record]['name']}.{host.host}"
|
|
658
658
|
|
|
659
|
-
r = azure.
|
|
659
|
+
r = azure.dns.RecordSet(
|
|
660
660
|
f"dns-comms-{suffix}-{host.identifier}-{record}",
|
|
661
661
|
resource_group_name=self._rg,
|
|
662
662
|
zone_name=host.zone.name,
|
|
663
663
|
relative_record_set_name=relative_record_set_name,
|
|
664
664
|
record_type="CNAME",
|
|
665
665
|
ttl=records[record]["ttl"],
|
|
666
|
-
cname_record=azure.
|
|
666
|
+
cname_record=azure.dns.CnameRecordArgs(cname=records[record]["value"]),
|
|
667
667
|
)
|
|
668
668
|
created_records.append(r)
|
|
669
669
|
|
|
@@ -1026,7 +1026,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
1026
1026
|
if host.zone:
|
|
1027
1027
|
# Create a DNS record in the zone.
|
|
1028
1028
|
# We always use an A record instead of CNAME to avoid collisions with TXT records.
|
|
1029
|
-
a = azure.
|
|
1029
|
+
a = azure.dns.RecordSet(
|
|
1030
1030
|
f"dns-a-{name}-{self._name}-{host.identifier}",
|
|
1031
1031
|
resource_group_name=self._rg,
|
|
1032
1032
|
zone_name=host.zone.name,
|
|
@@ -1034,7 +1034,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
1034
1034
|
record_type="A",
|
|
1035
1035
|
ttl=3600,
|
|
1036
1036
|
a_records=[
|
|
1037
|
-
azure.
|
|
1037
|
+
azure.dns.ARecordArgs(
|
|
1038
1038
|
ipv4_address=virtual_ip,
|
|
1039
1039
|
)
|
|
1040
1040
|
],
|
|
@@ -1045,7 +1045,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
1045
1045
|
# For the certificate validation to work
|
|
1046
1046
|
relative_record_set_name = "asuid" if host.host == "@" else pulumi.Output.concat("asuid.", host.host)
|
|
1047
1047
|
|
|
1048
|
-
txt_validation = azure.
|
|
1048
|
+
txt_validation = azure.dns.RecordSet(
|
|
1049
1049
|
f"dns-txt-{name}-{self._name}-{host.identifier}",
|
|
1050
1050
|
resource_group_name=self._rg,
|
|
1051
1051
|
zone_name=host.zone.name,
|
|
@@ -1053,7 +1053,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
1053
1053
|
record_type="TXT",
|
|
1054
1054
|
ttl=3600,
|
|
1055
1055
|
txt_records=[
|
|
1056
|
-
azure.
|
|
1056
|
+
azure.dns.TxtRecordArgs(
|
|
1057
1057
|
value=[app.custom_domain_verification_id],
|
|
1058
1058
|
)
|
|
1059
1059
|
],
|
|
@@ -1126,7 +1126,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
1126
1126
|
# Grant the app access to the cache if needed.
|
|
1127
1127
|
# We need to check explicitly if it is not None because the db could also be 0.
|
|
1128
1128
|
if self._cache and cache_db is not None:
|
|
1129
|
-
azure.
|
|
1129
|
+
azure.redis.AccessPolicyAssignment(
|
|
1130
1130
|
f"ra-{name}-cache",
|
|
1131
1131
|
resource_group_name=self._rg,
|
|
1132
1132
|
cache_name=self._cache.name,
|
|
@@ -1171,7 +1171,7 @@ class DjangoDeployment(pulumi.ComponentResource):
|
|
|
1171
1171
|
|
|
1172
1172
|
return app
|
|
1173
1173
|
|
|
1174
|
-
def _strip_off_dns_zone_name(self, host: str, zone: azure.
|
|
1174
|
+
def _strip_off_dns_zone_name(self, host: str, zone: azure.dns.Zone) -> pulumi.Output[str]:
|
|
1175
1175
|
"""
|
|
1176
1176
|
Strip off the DNS zone name from the host name.
|
|
1177
1177
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django.core.cache import cache
|
|
2
|
+
from django.core.management.base import BaseCommand
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Command(BaseCommand):
|
|
6
|
+
help = "Purges the entire cache."
|
|
7
|
+
|
|
8
|
+
def handle(self, *args, **options):
|
|
9
|
+
self.stdout.write("Purging cache...")
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
cache.clear()
|
|
13
|
+
self.stdout.write(self.style.SUCCESS("Successfully purged cache."))
|
|
14
|
+
except Exception as e:
|
|
15
|
+
self.stdout.write(self.style.ERROR(f"Failed to purge cache: {e}"))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
|
|
3
4
|
from django.conf import settings
|
|
4
5
|
from django.core.cache import cache
|
|
@@ -7,35 +8,57 @@ from django.db.utils import OperationalError
|
|
|
7
8
|
from django.http import HttpResponse
|
|
8
9
|
from django_redis import get_redis_connection
|
|
9
10
|
|
|
10
|
-
from .azure_helper import get_db_password, get_redis_credentials
|
|
11
|
+
from .azure_helper import db_token_will_expire, get_db_password, get_redis_credentials, redis_token_will_expire
|
|
11
12
|
|
|
12
|
-
logger = logging.getLogger(
|
|
13
|
+
logger = logging.getLogger("pulumi_django_azure.health_check")
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class HealthCheckMiddleware:
|
|
16
17
|
def __init__(self, get_response):
|
|
17
18
|
self.get_response = get_response
|
|
18
19
|
|
|
20
|
+
def _self_heal(self):
|
|
21
|
+
# Send HUP signal to gunicorn main thread,
|
|
22
|
+
# which will trigger new workers to start.
|
|
23
|
+
os.kill(os.getppid(), 1)
|
|
24
|
+
|
|
19
25
|
def __call__(self, request):
|
|
20
26
|
if request.path == settings.HEALTH_CHECK_PATH:
|
|
21
27
|
# Update the database credentials if needed
|
|
22
28
|
if settings.AZURE_DB_PASSWORD:
|
|
23
29
|
try:
|
|
24
|
-
|
|
30
|
+
if db_token_will_expire():
|
|
31
|
+
logger.debug("Database token will expire, fetching new credentials")
|
|
32
|
+
settings.DATABASES["default"]["PASSWORD"] = get_db_password()
|
|
33
|
+
else:
|
|
34
|
+
logger.debug("Database token is still valid, skipping credentials update")
|
|
25
35
|
except Exception as e:
|
|
26
36
|
logger.error("Failed to update database credentials: %s", str(e))
|
|
37
|
+
|
|
38
|
+
self._self_heal()
|
|
39
|
+
|
|
27
40
|
return HttpResponse(status=503)
|
|
28
41
|
|
|
29
42
|
# Update the Redis credentials if needed
|
|
30
43
|
if settings.AZURE_REDIS_CREDENTIALS:
|
|
31
44
|
try:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
if redis_token_will_expire():
|
|
46
|
+
logger.debug("Redis token will expire, fetching new credentials")
|
|
47
|
+
|
|
48
|
+
redis_credentials = get_redis_credentials()
|
|
49
|
+
|
|
50
|
+
# Re-authenticate the Redis connection
|
|
51
|
+
redis_connection = get_redis_connection("default")
|
|
52
|
+
redis_connection.execute_command("AUTH", redis_credentials.username, redis_credentials.password)
|
|
53
|
+
|
|
54
|
+
settings.CACHES["default"]["OPTIONS"]["PASSWORD"] = redis_credentials.password
|
|
55
|
+
else:
|
|
56
|
+
logger.debug("Redis token is still valid, skipping credentials update")
|
|
37
57
|
except Exception as e:
|
|
38
58
|
logger.error("Failed to update Redis credentials: %s", str(e))
|
|
59
|
+
|
|
60
|
+
self._self_heal()
|
|
61
|
+
|
|
39
62
|
return HttpResponse(status=503)
|
|
40
63
|
|
|
41
64
|
try:
|
pulumi_django_azure/settings.py
CHANGED
|
@@ -102,14 +102,21 @@ if IS_AZURE_ENVIRONMENT:
|
|
|
102
102
|
LOGGING = {
|
|
103
103
|
"version": 1,
|
|
104
104
|
"disable_existing_loggers": False,
|
|
105
|
+
"formatters": {
|
|
106
|
+
"timestamped": {
|
|
107
|
+
"format": "{asctime} {levelname} {message}",
|
|
108
|
+
"style": "{",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
105
111
|
"handlers": {
|
|
106
112
|
"file": {
|
|
107
113
|
"level": "INFO",
|
|
108
|
-
"class": "logging.handlers.
|
|
114
|
+
"class": "logging.handlers.RotatingFileHandler",
|
|
109
115
|
"filename": "/home/LogFiles/django.log",
|
|
110
|
-
|
|
111
|
-
"
|
|
112
|
-
"backupCount":
|
|
116
|
+
# 50 MB
|
|
117
|
+
"maxBytes": 52428800,
|
|
118
|
+
"backupCount": 5,
|
|
119
|
+
"formatter": "timestamped",
|
|
113
120
|
},
|
|
114
121
|
},
|
|
115
122
|
"loggers": {
|
|
@@ -118,6 +125,11 @@ if IS_AZURE_ENVIRONMENT:
|
|
|
118
125
|
"level": "INFO",
|
|
119
126
|
"propagate": True,
|
|
120
127
|
},
|
|
128
|
+
"pulumi_django_azure": {
|
|
129
|
+
"handlers": ["file"],
|
|
130
|
+
"level": "INFO",
|
|
131
|
+
"propagate": True,
|
|
132
|
+
},
|
|
121
133
|
},
|
|
122
134
|
}
|
|
123
135
|
|
|
@@ -152,3 +164,5 @@ if redis_cache_host and redis_cache_port and redis_cache_db:
|
|
|
152
164
|
},
|
|
153
165
|
},
|
|
154
166
|
}
|
|
167
|
+
else:
|
|
168
|
+
AZURE_REDIS_CREDENTIALS = False
|
|
@@ -1,52 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pulumi-django-azure
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.27
|
|
4
4
|
Summary: Simply deployment of Django on Azure with Pulumi
|
|
5
5
|
Author-email: Maarten Ureel <maarten@youreal.eu>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2023 YouReal BV
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Project-URL: Homepage, https://gitlab.com/MaartenUreel/pulumi-django-azure
|
|
29
8
|
Keywords: django,pulumi,azure
|
|
30
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
31
9
|
Classifier: Programming Language :: Python
|
|
32
10
|
Classifier: Programming Language :: Python :: 3
|
|
33
11
|
Requires-Python: <3.14,>=3.11
|
|
34
12
|
Description-Content-Type: text/markdown
|
|
35
|
-
License-File: LICENSE
|
|
36
13
|
Requires-Dist: azure-identity<2.0.0,>=1.21.0
|
|
37
14
|
Requires-Dist: azure-keyvault-secrets<5.0.0,>=4.9.0
|
|
38
15
|
Requires-Dist: azure-mgmt-cdn<14.0.0,>=13.1.1
|
|
39
16
|
Requires-Dist: azure-mgmt-resource<24.0.0,>=23.3.0
|
|
40
|
-
Requires-Dist: django<6.0
|
|
41
|
-
Requires-Dist: django-azure-communication-email<2.0.0,>=1.3.
|
|
17
|
+
Requires-Dist: django<6.0,>=5.2
|
|
18
|
+
Requires-Dist: django-azure-communication-email<2.0.0,>=1.3.2
|
|
42
19
|
Requires-Dist: django-environ<0.13.0,>=0.12.0
|
|
43
20
|
Requires-Dist: django-redis<6.0.0,>=5.4.0
|
|
44
|
-
Requires-Dist: django-storages[azure]<2.0.0,>=1.14.
|
|
45
|
-
Requires-Dist: pulumi>=3.
|
|
46
|
-
Requires-Dist: pulumi-azure-native>=2.
|
|
21
|
+
Requires-Dist: django-storages[azure]<2.0.0,>=1.14.6
|
|
22
|
+
Requires-Dist: pulumi>=3.163.0
|
|
23
|
+
Requires-Dist: pulumi-azure-native>=3.2.0
|
|
47
24
|
Requires-Dist: pulumi-random>=4.18.0
|
|
48
25
|
Requires-Dist: redis[hiredis]<6.0.0,>=5.2.1
|
|
49
|
-
Dynamic: license-file
|
|
50
26
|
|
|
51
27
|
# Pulumi Django Deployment
|
|
52
28
|
|
|
@@ -76,7 +52,7 @@ Your Django project should contain a folder `cicd` with these files:
|
|
|
76
52
|
sh cicd/collectstatic.sh &
|
|
77
53
|
|
|
78
54
|
python manage.py migrate
|
|
79
|
-
gunicorn --timeout 600 --workers $((($NUM_CORES*2)+1)) --chdir $APP_PATH yourapplication.wsgi --access-logfile '-' --error-logfile '-'
|
|
55
|
+
gunicorn --timeout 600 --workers $((($NUM_CORES*2)+1)) --preload --chdir $APP_PATH yourapplication.wsgi --access-logfile '-' --error-logfile '-'
|
|
80
56
|
```
|
|
81
57
|
|
|
82
58
|
Be sure to change `yourapplication` in the above.
|
|
@@ -168,7 +144,7 @@ django.add_database_administrator(
|
|
|
168
144
|
```python
|
|
169
145
|
from pulumi_django_azure.settings import * # noqa: F403
|
|
170
146
|
|
|
171
|
-
# This will provide the management command to purge the CDN
|
|
147
|
+
# This will provide the management command to purge the CDN and cache
|
|
172
148
|
INSTALLED_APPS += ["pulumi_django_azure"]
|
|
173
149
|
|
|
174
150
|
# This will provide the health check middleware that will also take care of credential rotation.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pulumi_django_azure/__init__.py,sha256=WoTHLNGnqc3dQoJtzrAJY8OVA7ReP6XFkDb9BXZGfJ8,117
|
|
2
|
+
pulumi_django_azure/azure_helper.py,sha256=eIaOoajwQuIRyt6Q3KHIA1qctLxOqwSF9yurwk2WgzY,3417
|
|
3
|
+
pulumi_django_azure/django_deployment.py,sha256=9K_UsZ6fwQgMnDRrKjQ2hWGFC2QX6W6jujdIAixx2y8,49674
|
|
4
|
+
pulumi_django_azure/middleware.py,sha256=Lp05G_wn9vlSueRJsnk-g2Rs9FFG155Po2MP2Cv0LDc,3348
|
|
5
|
+
pulumi_django_azure/settings.py,sha256=BTP5y6KE3oP9pY01TR1ML6GArpLhnzx-mFflrhTQ4SE,5906
|
|
6
|
+
pulumi_django_azure/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
pulumi_django_azure/management/commands/purge_cache.py,sha256=yjMoNvEPFmtuZctOKXMUacfKJLlTC9xfF-0W-IH3kF0,488
|
|
8
|
+
pulumi_django_azure/management/commands/purge_cdn.py,sha256=1lZI5liDIiOGyJRmo9gRn-BDLHaRuipaUmUAm06bRr0,2093
|
|
9
|
+
pulumi_django_azure-1.0.27.dist-info/METADATA,sha256=2k5FPkCereVU1U-EnVGGYn27P_-XSYZkyQOmcDU555c,11212
|
|
10
|
+
pulumi_django_azure-1.0.27.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
|
11
|
+
pulumi_django_azure-1.0.27.dist-info/top_level.txt,sha256=MNPRJhq-_G8EMCHRkjdcb_xrqzOkmKogXUGV7Ysz3g0,20
|
|
12
|
+
pulumi_django_azure-1.0.27.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
pulumi_django_azure/__init__.py,sha256=WoTHLNGnqc3dQoJtzrAJY8OVA7ReP6XFkDb9BXZGfJ8,117
|
|
2
|
-
pulumi_django_azure/azure_helper.py,sha256=HU0cb85jTeh70qjv11foWuqgE-SNQ21bhMKsy1lB7MM,1903
|
|
3
|
-
pulumi_django_azure/django_deployment.py,sha256=VL9MTEq5rDLGdJPDQWHHCGbeC3Taw7vnpq_DGoMA6Ec,49705
|
|
4
|
-
pulumi_django_azure/middleware.py,sha256=aifo-Y8uksqNuIjTV9u_r4rMBczG-BNvOOGUx8j-v2E,2446
|
|
5
|
-
pulumi_django_azure/settings.py,sha256=hC1to1sgKeA7Lw43PlCx4AJ1AOQLuXbAhDIprD3uKc8,5482
|
|
6
|
-
pulumi_django_azure/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
pulumi_django_azure/management/commands/purge_cdn.py,sha256=1lZI5liDIiOGyJRmo9gRn-BDLHaRuipaUmUAm06bRr0,2093
|
|
8
|
-
pulumi_django_azure-1.0.25.dist-info/licenses/LICENSE,sha256=NX2LN3U319Zaac8b7ZgfNOco_nTBbN531X_M_13niSg,1087
|
|
9
|
-
pulumi_django_azure-1.0.25.dist-info/METADATA,sha256=s6tFNR8gtweJKOzpO6sKkxLBoERTWIZFWWYS6YdlkY4,12537
|
|
10
|
-
pulumi_django_azure-1.0.25.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
11
|
-
pulumi_django_azure-1.0.25.dist-info/top_level.txt,sha256=MNPRJhq-_G8EMCHRkjdcb_xrqzOkmKogXUGV7Ysz3g0,20
|
|
12
|
-
pulumi_django_azure-1.0.25.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2023 YouReal BV
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
File without changes
|