pulumi-django-azure 1.0.21__py3-none-any.whl → 1.0.22__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.

@@ -1 +1,2 @@
1
+ from . import settings # noqa: F401
1
2
  from .django_deployment import DjangoDeployment, HostDefinition # noqa: F401
@@ -0,0 +1,67 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ from dataclasses import dataclass
5
+ from subprocess import check_output
6
+
7
+ from azure.identity import DefaultAzureCredential
8
+ from azure.mgmt.resource import SubscriptionClient
9
+ from azure.mgmt.resource.subscriptions.models import Subscription
10
+
11
+ # Azure credentials
12
+ AZURE_CREDENTIAL = DefaultAzureCredential()
13
+
14
+ # Get the local IP addresses of the machine (only when runnig on Azure)
15
+ if os.environ.get("ORYX_ENV_NAME"):
16
+ LOCAL_IP_ADDRESSES = check_output(["hostname", "--all-ip-addresses"]).decode("utf-8").strip().split(" ")
17
+ else:
18
+ LOCAL_IP_ADDRESSES = []
19
+
20
+
21
+ def get_db_password() -> str:
22
+ """
23
+ Get a valid password for the database.
24
+ """
25
+ return AZURE_CREDENTIAL.get_token("https://ossrdbms-aad.database.windows.net/.default").token
26
+
27
+
28
+ @dataclass
29
+ class RedisCredentials:
30
+ username: str
31
+ password: str
32
+
33
+
34
+ def get_redis_credentials() -> RedisCredentials:
35
+ """
36
+ Get valid credentials for the Redis cache.
37
+ """
38
+ token = AZURE_CREDENTIAL.get_token("https://redis.azure.com/.default").token
39
+ return RedisCredentials(_extract_username_from_token(token), token)
40
+
41
+
42
+ def get_subscription() -> Subscription:
43
+ """
44
+ Get the subscription for the current user.
45
+ """
46
+ subscription_client = SubscriptionClient(AZURE_CREDENTIAL)
47
+ subscriptions = list(subscription_client.subscriptions.list())
48
+ return subscriptions[0]
49
+
50
+
51
+ def _extract_username_from_token(token: str) -> str:
52
+ """
53
+ Extract the username from the JSON Web Token (JWT) token.
54
+ """
55
+ parts = token.split(".")
56
+ base64_str = parts[1]
57
+
58
+ if len(base64_str) % 4 == 2:
59
+ base64_str += "=="
60
+ elif len(base64_str) % 4 == 3:
61
+ base64_str += "="
62
+
63
+ json_bytes = base64.b64decode(base64_str)
64
+ json_str = json_bytes.decode("utf-8")
65
+ jwt = json.loads(json_str)
66
+
67
+ return jwt["oid"]
@@ -1,5 +1,4 @@
1
1
  from collections.abc import Sequence
2
- from typing import Optional, Union
3
2
 
4
3
  import pulumi
5
4
  import pulumi_azure_native as azure
@@ -15,7 +14,7 @@ class HostDefinition:
15
14
  :param identifier: An identifier for this host definition (optional).
16
15
  """
17
16
 
18
- def __init__(self, host: str, zone: Optional[azure.network.Zone] = None, identifier: Optional[str] = None):
17
+ def __init__(self, host: str, zone: azure.network.Zone | None = None, identifier: str | None = None):
19
18
  self.host = host
20
19
  self.zone = zone
21
20
  self._identifier = identifier
@@ -68,12 +67,12 @@ class DjangoDeployment(pulumi.ComponentResource):
68
67
  app_service_ip_prefix: str,
69
68
  app_service_sku: azure.web.SkuDescriptionArgs,
70
69
  storage_account_name: str,
71
- storage_allowed_origins: Optional[Sequence[str]] = None,
72
- pgadmin_access_ip: Optional[Sequence[str]] = None,
73
- pgadmin_dns_zone: Optional[azure.network.Zone] = None,
74
- cache_ip_prefix: Optional[str] = None,
75
- cache_sku: Optional[azure.cache.SkuArgs] = None,
76
- cdn_host: Optional[HostDefinition] = None,
70
+ storage_allowed_origins: Sequence[str] | None = None,
71
+ pgadmin_access_ip: Sequence[str] | None = None,
72
+ pgadmin_dns_zone: azure.network.Zone | None = None,
73
+ cache_ip_prefix: str | None = None,
74
+ cache_sku: azure.cache.SkuArgs | None = None,
75
+ cdn_host: HostDefinition | None = None,
77
76
  opts=None,
78
77
  ):
79
78
  """
@@ -134,7 +133,7 @@ class DjangoDeployment(pulumi.ComponentResource):
134
133
  # Create a pgAdmin app
135
134
  self._create_pgadmin_app(access_ip=pgadmin_access_ip, dns_zone=pgadmin_dns_zone)
136
135
 
137
- def _create_storage(self, account_name: str, allowed_origins: Optional[Sequence[str]] = None):
136
+ def _create_storage(self, account_name: str, allowed_origins: Sequence[str] | None = None):
138
137
  # Create blob storage
139
138
  self._storage_account = azure.storage.StorageAccount(
140
139
  f"sa-{self._name}",
@@ -169,7 +168,7 @@ class DjangoDeployment(pulumi.ComponentResource):
169
168
  ),
170
169
  )
171
170
 
172
- def _create_cdn(self, custom_host: Optional[HostDefinition]) -> pulumi.Output[str]:
171
+ def _create_cdn(self, custom_host: HostDefinition | None) -> pulumi.Output[str]:
173
172
  """
174
173
  Create a CDN endpoint. If a host name is given, it will be used as the custom domain.
175
174
  Otherwise, the default CDN host name will be returned.
@@ -392,7 +391,7 @@ class DjangoDeployment(pulumi.ComponentResource):
392
391
  self,
393
392
  name,
394
393
  prefix,
395
- delegation_service: Optional[str] = None,
394
+ delegation_service: str | None = None,
396
395
  service_endpoints: Sequence[str] = [],
397
396
  ) -> azure.network.Subnet:
398
397
  """
@@ -433,7 +432,7 @@ class DjangoDeployment(pulumi.ComponentResource):
433
432
  sku=sku,
434
433
  )
435
434
 
436
- def _create_pgadmin_app(self, access_ip: Optional[Sequence[str]] = None, dns_zone: Optional[azure.network.Zone] = None):
435
+ def _create_pgadmin_app(self, access_ip: Sequence[str] | None = None, dns_zone: azure.network.Zone | None = None):
437
436
  # Determine the IP restrictions
438
437
  ip_restrictions = []
439
438
  default_restriction = azure.web.DefaultAction.ALLOW
@@ -562,10 +561,10 @@ class DjangoDeployment(pulumi.ComponentResource):
562
561
  def _add_webapp_host(
563
562
  self,
564
563
  app: azure.web.WebApp,
565
- host: Union[str, pulumi.Input[str]],
564
+ host: str | pulumi.Input[str],
566
565
  suffix: str,
567
566
  identifier: str,
568
- depends_on: Optional[Sequence[pulumi.Resource]] = None,
567
+ depends_on: Sequence[pulumi.Resource] | None = None,
569
568
  ):
570
569
  """
571
570
  Because of a circular dependency, we need to create the certificate and the binding in two steps.
@@ -838,13 +837,13 @@ class DjangoDeployment(pulumi.ComponentResource):
838
837
  website_hosts: list[HostDefinition],
839
838
  django_settings_module: str,
840
839
  python_version: str = "3.13",
841
- environment_variables: Optional[dict[str, str]] = None,
842
- secrets: Optional[dict[str, str]] = None,
843
- comms_data_location: Optional[str] = None,
844
- comms_domains: Optional[list[HostDefinition]] = None,
845
- dedicated_app_service_sku: Optional[azure.web.SkuDescriptionArgs] = None,
846
- vault_administrators: Optional[list[str]] = None,
847
- cache_db: Optional[int] = None,
840
+ environment_variables: dict[str, str] | None = None,
841
+ secrets: dict[str, str] | None = None,
842
+ comms_data_location: str | None = None,
843
+ comms_domains: list[HostDefinition] | None = None,
844
+ dedicated_app_service_sku: azure.web.SkuDescriptionArgs | None = None,
845
+ vault_administrators: list[str] | None = None,
846
+ cache_db: int | None = None,
848
847
  ) -> azure.web.WebApp:
849
848
  """
850
849
  Create a Django website with it's own database and storage containers.
File without changes
@@ -0,0 +1,52 @@
1
+ import os
2
+
3
+ from azure.mgmt.cdn import CdnManagementClient
4
+ from azure.mgmt.cdn.models import PurgeParameters
5
+ from django.conf import settings
6
+ from django.core.management.base import BaseCommand
7
+ from pulumi_django_azure.azure_helper import AZURE_CREDENTIAL
8
+
9
+
10
+ class Command(BaseCommand):
11
+ help = "Purges the CDN endpoint"
12
+
13
+ def add_arguments(self, parser):
14
+ parser.add_argument(
15
+ "--wait",
16
+ action="store_true",
17
+ help="Wait for the purge operation to complete",
18
+ )
19
+
20
+ def handle(self, *args, **options):
21
+ # Read environment variables
22
+ resource_group = os.getenv("WEBSITE_RESOURCE_GROUP")
23
+ profile_name = os.getenv("CDN_PROFILE")
24
+ endpoint_name = os.getenv("CDN_ENDPOINT")
25
+ content_paths = ["/*"]
26
+
27
+ # Ensure all required environment variables are set
28
+ if not all([resource_group, profile_name, endpoint_name]):
29
+ self.stderr.write(self.style.ERROR("Missing required environment variables."))
30
+ return
31
+
32
+ # Authenticate with Azure
33
+ cdn_client = CdnManagementClient(AZURE_CREDENTIAL, settings.AZURE_SUBSCRIPTION_ID)
34
+
35
+ try:
36
+ # Purge the CDN endpoint
37
+ purge_operation = cdn_client.endpoints.begin_purge_content(
38
+ resource_group_name=resource_group,
39
+ profile_name=profile_name,
40
+ endpoint_name=endpoint_name,
41
+ content_file_paths=PurgeParameters(content_paths=content_paths),
42
+ )
43
+
44
+ # Check if the --wait argument was provided
45
+ if options["wait"]:
46
+ purge_operation.result() # Wait for the operation to complete
47
+ self.stdout.write(self.style.SUCCESS("CDN endpoint purge operation completed successfully."))
48
+ else:
49
+ self.stdout.write(self.style.SUCCESS("CDN endpoint purge operation started successfully."))
50
+
51
+ except Exception as e:
52
+ self.stderr.write(self.style.ERROR(f"Error executing CDN endpoint purge command: {e}"))
@@ -0,0 +1,43 @@
1
+ from django.conf import settings
2
+ from django.core.cache import cache
3
+ from django.db import connection
4
+ from django.db.utils import OperationalError
5
+ from django.http import HttpResponse
6
+ from django_redis import get_redis_connection
7
+
8
+ from .azure_helper import get_db_password, get_redis_credentials
9
+
10
+
11
+ class HealthCheckMiddleware:
12
+ def __init__(self, get_response):
13
+ self.get_response = get_response
14
+
15
+ def __call__(self, request):
16
+ if request.path == settings.HEALTH_CHECK_PATH:
17
+ # Update the database credentials if needed
18
+ if settings.AZURE_DB_PASSWORD:
19
+ settings.DATABASES["default"]["PASSWORD"] = get_db_password()
20
+
21
+ # Update the Redis credentials if needed
22
+ if settings.AZURE_REDIS_CREDENTIALS:
23
+ redis_credentials = get_redis_credentials()
24
+
25
+ # Re-authenticate the Redis connection
26
+ redis_connection = get_redis_connection("default")
27
+ redis_connection.execute_command("AUTH", redis_credentials.username, redis_credentials.password)
28
+
29
+ settings.CACHES["default"]["OPTIONS"]["PASSWORD"] = redis_credentials.password
30
+
31
+ try:
32
+ # Test the database connection
33
+ connection.ensure_connection()
34
+
35
+ # Test the Redis connection
36
+ cache.set("health_check", "test")
37
+
38
+ return HttpResponse("OK")
39
+
40
+ except OperationalError:
41
+ return HttpResponse(status=503)
42
+
43
+ return self.get_response(request)
@@ -0,0 +1,137 @@
1
+ import environ
2
+ from azure.keyvault.secrets import SecretClient
3
+
4
+ from .azure_helper import AZURE_CREDENTIAL, LOCAL_IP_ADDRESSES, get_db_password, get_redis_credentials, get_subscription
5
+
6
+ env = environ.Env()
7
+
8
+ SECRET_KEY = env("DJANGO_SECRET_KEY")
9
+
10
+ # Health check path
11
+ HEALTH_CHECK_PATH = env("HEALTH_CHECK_PATH", default="/health")
12
+
13
+ ALLOWED_HOSTS: list = env.list("DJANGO_ALLOWED_HOSTS", default=[])
14
+ # WEBSITE_HOSTNAME contains the Azure domain name
15
+ ALLOWED_HOSTS.append(env("WEBSITE_HOSTNAME"))
16
+ # Add the local IP addresses of the machine for health checks
17
+ ALLOWED_HOSTS.extend(LOCAL_IP_ADDRESSES)
18
+
19
+ # Detect HTTPS behind AppService
20
+ SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
21
+
22
+
23
+ # Azure context
24
+ AZURE_SUBSCRIPTION = get_subscription()
25
+ AZURE_TENANT_ID = AZURE_SUBSCRIPTION.tenant_id
26
+ AZURE_SUBSCRIPTION_ID = AZURE_SUBSCRIPTION.subscription_id
27
+
28
+ # Azure Key Vault
29
+ AZURE_KEY_VAULT = env("AZURE_KEY_VAULT")
30
+ AZURE_KEY_VAULT_URI = f"https://{AZURE_KEY_VAULT}.vault.azure.net"
31
+ AZURE_KEY_VAULT_CLIENT = SecretClient(vault_url=AZURE_KEY_VAULT_URI, credential=AZURE_CREDENTIAL)
32
+
33
+ # Allow CSRF cookies to be sent from our domains
34
+ # CSRF_TRUSTED_ORIGINS = ["https://" + host for host in ALLOWED_HOSTS]
35
+ AZURE_ACCOUNT_NAME = env("AZURE_STORAGE_ACCOUNT_NAME")
36
+ AZURE_TOKEN_CREDENTIAL = AZURE_CREDENTIAL
37
+
38
+
39
+ # CDN domain - shared for all storages
40
+ AZURE_CUSTOM_DOMAIN = env("CDN_HOST")
41
+
42
+ STORAGES = {
43
+ "default": {
44
+ "BACKEND": "storages.backends.azure_storage.AzureStorage",
45
+ "OPTIONS": {
46
+ "azure_container": env("AZURE_STORAGE_CONTAINER_MEDIA"),
47
+ "overwrite_files": False,
48
+ },
49
+ },
50
+ "staticfiles": {
51
+ "BACKEND": "storages.backends.azure_storage.AzureStorage",
52
+ "OPTIONS": {
53
+ "azure_container": env("AZURE_STORAGE_CONTAINER_STATICFILES"),
54
+ },
55
+ },
56
+ }
57
+
58
+
59
+ # STATIC_ROOT = BASE_DIR / "staticfiles"
60
+ STATIC_URL = f"https://{AZURE_CUSTOM_DOMAIN}/static/"
61
+ MEDIA_URL = f"https://{AZURE_CUSTOM_DOMAIN}/media/"
62
+
63
+ # This setting enables password rotation in the health check middleware
64
+ AZURE_DB_PASSWORD = True
65
+ DATABASES = {
66
+ "default": {
67
+ "ENGINE": "django.db.backends.postgresql",
68
+ "NAME": env("DB_NAME"),
69
+ "USER": env("DB_USER"),
70
+ "HOST": env("DB_HOST"),
71
+ "PASSWORD": get_db_password(),
72
+ "PORT": "5432",
73
+ "OPTIONS": {
74
+ "sslmode": "require",
75
+ },
76
+ # Make connections persistent
77
+ "CONN_MAX_AGE": None,
78
+ # To enable health checks, add the following:
79
+ # "CONN_HEALTH_CHECKS": True,
80
+ }
81
+ }
82
+
83
+ # Email
84
+ EMAIL_BACKEND = "django_azure_communication_email.EmailBackend"
85
+ AZURE_COMMUNICATION_ENDPOINT = env("AZURE_COMMUNICATION_SERVICE_ENDPOINT")
86
+ DEFAULT_FROM_EMAIL = env("DJANGO_DEFAULT_FROM_EMAIL")
87
+
88
+
89
+ # Logging
90
+ LOGGING = {
91
+ "version": 1,
92
+ "disable_existing_loggers": False,
93
+ "handlers": {
94
+ "file": {
95
+ "level": "INFO",
96
+ "class": "logging.handlers.RotatingFileHandler",
97
+ "filename": "/home/LogFiles/django.log",
98
+ "maxBytes": 1024 * 1024 * 100, # 100 mb
99
+ "backupCount": 5,
100
+ },
101
+ },
102
+ "loggers": {
103
+ "django": {
104
+ "handlers": ["file"],
105
+ "level": "INFO",
106
+ "propagate": True,
107
+ },
108
+ },
109
+ }
110
+
111
+ # Redis, if enabled
112
+ if env("REDIS_CACHE_HOST") and env("REDIS_CACHE_PORT") and env("REDIS_CACHE_DB"):
113
+ # This will enable the health check to update the Redis credentials
114
+ AZURE_REDIS_CREDENTIALS = True
115
+
116
+ # This will prevent the website from failing if Redis is not available
117
+ DJANGO_REDIS_IGNORE_EXCEPTIONS = True
118
+ DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
119
+
120
+ REDIS_CACHE_HOST = env("REDIS_CACHE_HOST")
121
+ REDIS_CACHE_PORT = env("REDIS_CACHE_PORT")
122
+ REDIS_CACHE_DB = env("REDIS_CACHE_DB")
123
+ redis_credentials = get_redis_credentials()
124
+ REDIS_USERNAME = redis_credentials.username
125
+ REDIS_PASSWORD = redis_credentials.password
126
+
127
+ CACHES = {
128
+ "default": {
129
+ "BACKEND": "django_redis.cache.RedisCache",
130
+ "LOCATION": f"rediss://{REDIS_USERNAME}@{REDIS_CACHE_HOST}:{REDIS_CACHE_PORT}/{REDIS_CACHE_DB}",
131
+ "OPTIONS": {
132
+ "CLIENT_CLASS": "django_redis.client.DefaultClient",
133
+ "PARSER_CLASS": "redis.connection._HiredisParser",
134
+ "PASSWORD": REDIS_PASSWORD,
135
+ },
136
+ },
137
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pulumi-django-azure
3
- Version: 1.0.21
3
+ Version: 1.0.22
4
4
  Summary: Simply deployment of Django on Azure with Pulumi
5
5
  Author-email: Maarten Ureel <maarten@youreal.eu>
6
6
  License: MIT License
@@ -30,12 +30,22 @@ Keywords: django,pulumi,azure
30
30
  Classifier: License :: OSI Approved :: MIT License
31
31
  Classifier: Programming Language :: Python
32
32
  Classifier: Programming Language :: Python :: 3
33
- Requires-Python: >=3.9
33
+ Requires-Python: <3.14,>=3.11
34
34
  Description-Content-Type: text/markdown
35
35
  License-File: LICENSE
36
- Requires-Dist: pulumi>=3.146.0
37
- Requires-Dist: pulumi-azure-native>=2.82.0
38
- Requires-Dist: pulumi-random>=4.17.0
36
+ Requires-Dist: azure-identity<2.0.0,>=1.21.0
37
+ Requires-Dist: azure-keyvault-secrets<5.0.0,>=4.9.0
38
+ Requires-Dist: azure-mgmt-cdn<14.0.0,>=13.1.1
39
+ Requires-Dist: azure-mgmt-resource<24.0.0,>=23.3.0
40
+ Requires-Dist: django<6.0.0,>=5.1.7
41
+ Requires-Dist: django-azure-communication-email<2.0.0,>=1.3.0
42
+ Requires-Dist: django-environ<0.13.0,>=0.12.0
43
+ Requires-Dist: django-redis<6.0.0,>=5.4.0
44
+ Requires-Dist: django-storages[azure]<2.0.0,>=1.14.5
45
+ Requires-Dist: pulumi>=3.156.0
46
+ Requires-Dist: pulumi-azure-native>=2.89.1
47
+ Requires-Dist: pulumi-random>=4.18.0
48
+ Requires-Dist: redis[hiredis]<6.0.0,>=5.2.1
39
49
 
40
50
  # Pulumi Django Deployment
41
51
 
@@ -57,12 +67,36 @@ Your Django project should contain a folder `cicd` with these files:
57
67
  Note that this runs in the identity of the build container, so you should not run database or storage manipulations here.
58
68
  * startup.sh: commands to run the actual application. I recommend to put at least:
59
69
  ```bash
70
+ # Compile translations
71
+ apt-get -y install gettext
72
+ python manage.py compilemessages
73
+
74
+ # Run collectstatic in the background
75
+ sh cicd/collectstatic.sh &
76
+
60
77
  python manage.py migrate
61
- python manage.py collectstatic --noinput
62
- python manage.py purge_cdn
63
78
  gunicorn --timeout 600 --workers $((($NUM_CORES*2)+1)) --chdir $APP_PATH yourapplication.wsgi --access-logfile '-' --error-logfile '-'
64
79
  ```
65
- Be sure to change `yourapplication` in the above. To see the `purge_cdn` management command we use here, see below.
80
+
81
+ Be sure to change `yourapplication` in the above.
82
+
83
+ * collecstatic.sh
84
+ ```bash
85
+ #!/bin/bash
86
+
87
+ LOGFILE="/var/log/django_collectstatic.log"
88
+
89
+ # Collectstatic
90
+ echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting collectstatic" >> $LOGFILE
91
+ python manage.py collectstatic --noinput -v 2 >> $LOGFILE
92
+ echo "$(date '+%Y-%m-%d %H:%M:%S') - Finished collectstatic" >> $LOGFILE
93
+
94
+ # Purge the CDN
95
+ echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting purge_cdn" >> $LOGFILE
96
+ python manage.py purge_cdn >> $LOGFILE
97
+ echo "$(date '+%Y-%m-%d %H:%M:%S') - Finished purge_cdn" >> $LOGFILE
98
+ ```
99
+
66
100
  ## Installation
67
101
  This package is published on PyPi, so you can just add pulumi-django-azure to your requirements file.
68
102
 
@@ -127,8 +161,23 @@ django.add_database_administrator(
127
161
  )
128
162
  ```
129
163
 
130
- ## Deployment steps
164
+ ## Changes to your Django project
165
+ 1. Add `pulumi_django_azure` to your `INSTALLED_APPS`
166
+ 2. Add to your settings file:
167
+ ```python
168
+ from pulumi_django_azure.settings import * # noqa: F403
169
+
170
+ # This will provide the management command to purge the CDN
171
+ INSTALLED_APPS += ["pulumi_django_azure"]
172
+
173
+ # This will provide the health check middleware that will also take care of credential rotation.
174
+ MIDDLEWARE += ["pulumi_django_azure.middleware.HealthCheckMiddleware"]
175
+ ```
176
+ This will pre-configure most settings to make your app work on Azure. You can check the source for details,
177
+ and ofcourse override any value after importing them.
178
+
131
179
 
180
+ ## Deployment steps
132
181
  1. Deploy without custom hosts (for CDN and websites)
133
182
  2. Configure the PostgreSQL server (create and grant permissions to role for your websites)
134
183
  3. Retrieve the deployment SSH key and configure your remote GIT repository with it
@@ -211,19 +260,8 @@ The environment variable will be suffixed with `_SECRET_NAME`.
211
260
 
212
261
  Then, in your application, retrieve this data from the vault, e.g.:
213
262
  ```python
214
- from azure.keyvault.secrets import SecretClient
215
- from azure.identity import DefaultAzureCredential
216
-
217
- # Azure credentials
218
- azure_credential = DefaultAzureCredential()
219
-
220
- # Azure Key Vault
221
- AZURE_KEY_VAULT = env("AZURE_KEY_VAULT")
222
- AZURE_KEY_VAULT_URI = f"https://{AZURE_KEY_VAULT}.vault.azure.net"
223
- azure_key_vault_client = SecretClient(vault_url=AZURE_KEY_VAULT_URI, credential=azure_credential)
224
-
225
263
  # Social Auth settings
226
- oauth_secret = azure_key_vault_client.get_secret(env("AZURE_OAUTH_SECRET_NAME"))
264
+ oauth_secret = AZURE_KEY_VAULT_CLIENT.get_secret(env("AZURE_OAUTH_SECRET_NAME"))
227
265
  oauth_secret = json.loads(oauth_secret.value)
228
266
  SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY = oauth_secret["client_id"]
229
267
  SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET = oauth_secret["secret"]
@@ -257,81 +295,6 @@ Be sure to configure the SSH key that Azure will use on GitLab side. You can obt
257
295
 
258
296
  This would then trigger a redeploy everytime you make a commit to your live branch.
259
297
 
260
- ## CDN Purging
261
- We added a management command to Django to purge the CDN cache, and added that to the startup script. Our version is here:
262
- ```python
263
- import os
264
-
265
- from azure.mgmt.cdn import CdnManagementClient
266
- from azure.mgmt.cdn.models import PurgeParameters
267
- from django.core.management.base import BaseCommand
268
-
269
- from core.azure_helper import AZURE_CREDENTIAL, get_subscription_id
270
-
271
-
272
- class Command(BaseCommand):
273
- help = "Purges the CDN endpoint"
274
-
275
- def add_arguments(self, parser):
276
- parser.add_argument(
277
- "--wait",
278
- action="store_true",
279
- help="Wait for the purge operation to complete",
280
- )
281
-
282
- def handle(self, *args, **options):
283
- # Read environment variables
284
- resource_group = os.getenv("WEBSITE_RESOURCE_GROUP")
285
- profile_name = os.getenv("CDN_PROFILE")
286
- endpoint_name = os.getenv("CDN_ENDPOINT")
287
- content_paths = ["/*"]
288
-
289
- # Ensure all required environment variables are set
290
- if not all([resource_group, profile_name, endpoint_name]):
291
- self.stderr.write(self.style.ERROR("Missing required environment variables."))
292
- return
293
-
294
- # Authenticate with Azure
295
- cdn_client = CdnManagementClient(AZURE_CREDENTIAL, get_subscription_id())
296
-
297
- try:
298
- # Purge the CDN endpoint
299
- purge_operation = cdn_client.endpoints.begin_purge_content(
300
- resource_group_name=resource_group,
301
- profile_name=profile_name,
302
- endpoint_name=endpoint_name,
303
- content_file_paths=PurgeParameters(content_paths=content_paths),
304
- )
305
-
306
- # Check if the --wait argument was provided
307
- if options["wait"]:
308
- purge_operation.result() # Wait for the operation to complete
309
- self.stdout.write(self.style.SUCCESS("CDN endpoint purge operation completed successfully."))
310
- else:
311
- self.stdout.write(self.style.SUCCESS("CDN endpoint purge operation started successfully."))
312
-
313
- except Exception as e:
314
- self.stderr.write(self.style.ERROR(f"Error executing CDN endpoint purge command: {e}"))
315
- ```
316
-
317
- And our azure_helper:
318
- ```python
319
- from azure.identity import DefaultAzureCredential
320
- from azure.mgmt.resource import SubscriptionClient
321
-
322
- # Azure credentials
323
- AZURE_CREDENTIAL = DefaultAzureCredential()
324
-
325
-
326
- def get_db_password() -> str:
327
- return AZURE_CREDENTIAL.get_token("https://ossrdbms-aad.database.windows.net/.default").token
328
-
329
-
330
- def get_subscription_id() -> str:
331
- subscription_client = SubscriptionClient(AZURE_CREDENTIAL)
332
- subscriptions = list(subscription_client.subscriptions.list())
333
- return subscriptions[0].subscription_id
334
- ```
335
298
 
336
299
  ## Change requests
337
300
  I created this for internal use but since it took me a while to puzzle all the things together I decided to share it.
@@ -0,0 +1,12 @@
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=bSMczOl7nivLGVhfEXroDc_FyD0hXLne1TwhdhLMoNY,49260
4
+ pulumi_django_azure/middleware.py,sha256=AtZJO3NJLaf6Zdkbv2_SV9UeCZpXShMfomxpMEE0ab0,1546
5
+ pulumi_django_azure/settings.py,sha256=2Mu1k5bvx8LIgbGH8qI2RcKixyZW2njeHGUda39P4Xs,4496
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.22.dist-info/LICENSE,sha256=NX2LN3U319Zaac8b7ZgfNOco_nTBbN531X_M_13niSg,1087
9
+ pulumi_django_azure-1.0.22.dist-info/METADATA,sha256=uND6SLUYtIOnTqHsh7rf_p7QVVTQQqy-6y4pKLYXl2k,12514
10
+ pulumi_django_azure-1.0.22.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
11
+ pulumi_django_azure-1.0.22.dist-info/top_level.txt,sha256=MNPRJhq-_G8EMCHRkjdcb_xrqzOkmKogXUGV7Ysz3g0,20
12
+ pulumi_django_azure-1.0.22.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- pulumi_django_azure/__init__.py,sha256=5RY9reSVNw-HULrOXfhcq3cyPne-94ojFmeV1m6kIVg,79
2
- pulumi_django_azure/django_deployment.py,sha256=e4DHIoB3ibzFHAuK1lwaRCLrLly5DdhikQLuhOMYpBc,49365
3
- pulumi_django_azure-1.0.21.dist-info/LICENSE,sha256=NX2LN3U319Zaac8b7ZgfNOco_nTBbN531X_M_13niSg,1087
4
- pulumi_django_azure-1.0.21.dist-info/METADATA,sha256=QiN9s5lzA-moy_wwLxZSScUqj-p1goLfUIHmBrG7eQA,13955
5
- pulumi_django_azure-1.0.21.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
6
- pulumi_django_azure-1.0.21.dist-info/top_level.txt,sha256=MNPRJhq-_G8EMCHRkjdcb_xrqzOkmKogXUGV7Ysz3g0,20
7
- pulumi_django_azure-1.0.21.dist-info/RECORD,,