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

@@ -14,6 +14,7 @@ class DjangoDeployment(pulumi.ComponentResource):
14
14
  tenant_id: str,
15
15
  resource_group_name: pulumi.Input[str],
16
16
  vnet: azure.network.VirtualNetwork,
17
+ pgsql_sku: azure.dbforpostgresql.SkuArgs,
17
18
  pgsql_ip_prefix: str,
18
19
  appservice_ip_prefix: str,
19
20
  app_service_sku: azure.web.SkuDescriptionArgs,
@@ -28,6 +29,7 @@ class DjangoDeployment(pulumi.ComponentResource):
28
29
  :param tenant_id: The Entra tenant ID for the database authentication.
29
30
  :param resource_group_name: The resource group name to create the resources in.
30
31
  :param vnet: The virtual network to create the subnets in.
32
+ :param pgsql_sku: The SKU for the PostgreSQL server.
31
33
  :param pgsql_ip_prefix: The IP prefix for the PostgreSQL subnet.
32
34
  :param appservice_ip_prefix: The IP prefix for the app service subnet.
33
35
  :param app_service_sku: The SKU for the app service plan.
@@ -39,6 +41,7 @@ class DjangoDeployment(pulumi.ComponentResource):
39
41
  super().__init__("pkg:index:DjangoDeployment", name, None, opts)
40
42
 
41
43
  # child_opts = pulumi.ResourceOptions(parent=self)
44
+ self._config = pulumi.Config()
42
45
 
43
46
  self._name = name
44
47
  self._tenant_id = tenant_id
@@ -50,7 +53,7 @@ class DjangoDeployment(pulumi.ComponentResource):
50
53
  self._cdn_host = self._create_cdn(custom_host=cdn_host)
51
54
 
52
55
  # PostgreSQL resources
53
- self._create_database(ip_prefix=pgsql_ip_prefix)
56
+ self._create_database(sku=pgsql_sku, ip_prefix=pgsql_ip_prefix)
54
57
 
55
58
  # Subnet for the apps
56
59
  self._app_subnet = self._create_subnet(
@@ -92,7 +95,7 @@ class DjangoDeployment(pulumi.ComponentResource):
92
95
  """
93
96
 
94
97
  # Put CDN in front
95
- cdn = azure.cdn.Profile(
98
+ self._cdn_profile = azure.cdn.Profile(
96
99
  f"cdn-{self._name}",
97
100
  resource_group_name=self._rg,
98
101
  location="global",
@@ -122,7 +125,7 @@ class DjangoDeployment(pulumi.ComponentResource):
122
125
  ],
123
126
  is_http_allowed=False,
124
127
  is_https_allowed=True,
125
- profile_name=cdn.name,
128
+ profile_name=self._cdn_profile.name,
126
129
  origin_host_header=endpoint_origin,
127
130
  origins=[azure.cdn.DeepCreatedOriginArgs(name="origin-storage", host_name=endpoint_origin)],
128
131
  query_string_caching_behavior=azure.cdn.QueryStringCachingBehavior.IGNORE_QUERY_STRING,
@@ -135,7 +138,7 @@ class DjangoDeployment(pulumi.ComponentResource):
135
138
  azure.cdn.CustomDomain(
136
139
  f"cdn-custom-domain-{self._name}",
137
140
  resource_group_name=self._rg,
138
- profile_name=cdn.name,
141
+ profile_name=self._cdn_profile.name,
139
142
  endpoint_name=self._cdn_endpoint.name,
140
143
  host_name=custom_host,
141
144
  )
@@ -145,7 +148,7 @@ class DjangoDeployment(pulumi.ComponentResource):
145
148
  # Return the default CDN host name
146
149
  return self._cdn_endpoint.host_name
147
150
 
148
- def _create_database(self, ip_prefix: str):
151
+ def _create_database(self, sku: azure.dbforpostgresql.SkuArgs, ip_prefix: str):
149
152
  # Create subnet for PostgreSQL
150
153
  subnet = self._create_subnet(
151
154
  name="pgsql",
@@ -176,10 +179,7 @@ class DjangoDeployment(pulumi.ComponentResource):
176
179
  self._pgsql = azure.dbforpostgresql.Server(
177
180
  f"pgsql-{self._name}",
178
181
  resource_group_name=self._rg,
179
- sku=azure.dbforpostgresql.SkuArgs(
180
- name="Standard_B2s",
181
- tier=azure.dbforpostgresql.SkuTier.BURSTABLE,
182
- ),
182
+ sku=sku,
183
183
  version="16",
184
184
  auth_config=azure.dbforpostgresql.AuthConfigArgs(
185
185
  password_auth=azure.dbforpostgresql.PasswordAuthEnum.DISABLED,
@@ -260,8 +260,6 @@ class DjangoDeployment(pulumi.ComponentResource):
260
260
  # azure.web.NameValuePairArgs(name="WEBSITE_HTTPLOGGING_RETENTION_DAYS", value="7"),
261
261
  # pgAdmin settings
262
262
  azure.web.NameValuePairArgs(name="PGADMIN_DISABLE_POSTFIX", value="true"),
263
- azure.web.NameValuePairArgs(name="PGADMIN_AUTHENTICATION_SOURCES", value="['oauth2, 'internal']"),
264
- azure.web.NameValuePairArgs(name="PGADMIN_OAUTH2_NAME", value="azure"),
265
263
  azure.web.NameValuePairArgs(name="PGADMIN_DEFAULT_EMAIL", value="dbadmin@dbadmin.net"),
266
264
  azure.web.NameValuePairArgs(name="PGADMIN_DEFAULT_PASSWORD", value="dbadmin"),
267
265
  ],
@@ -348,6 +346,112 @@ class DjangoDeployment(pulumi.ComponentResource):
348
346
  host_name=host,
349
347
  )
350
348
 
349
+ def _add_webapp_comms(self, data_location: str, domains: list[str], suffix: str) -> azure.communication.CommunicationService:
350
+ email_service = azure.communication.EmailService(
351
+ f"comms-email-{suffix}",
352
+ resource_group_name=self._rg,
353
+ location="global",
354
+ data_location=data_location,
355
+ )
356
+
357
+ domain_resources = []
358
+ if domains:
359
+ # Add our own custom domains
360
+ for domain in domains:
361
+ safe_host = domain.replace(".", "-")
362
+ d = azure.communication.Domain(
363
+ f"comms-email-domain-{suffix}-{safe_host}",
364
+ resource_group_name=self._rg,
365
+ location="global",
366
+ domain_management=azure.communication.DomainManagement.CUSTOMER_MANAGED,
367
+ domain_name=domain,
368
+ email_service_name=email_service.name,
369
+ )
370
+ domain_resources.append(d.id.apply(lambda n: n))
371
+ else:
372
+ # Add an Azure managed domain
373
+ d = azure.communication.Domain(
374
+ f"comms-email-domain-{suffix}-azure",
375
+ resource_group_name=self._rg,
376
+ location="global",
377
+ domain_management=azure.communication.DomainManagement.AZURE_MANAGED,
378
+ domain_name="AzureManagedDomain",
379
+ email_service_name=email_service.name,
380
+ )
381
+ domain_resources.append(d.id.apply(lambda n: n))
382
+
383
+ # Create Communication Services and link the domains
384
+ comm_service = azure.communication.CommunicationService(
385
+ f"comms-{suffix}",
386
+ resource_group_name=self._rg,
387
+ location="global",
388
+ data_location=data_location,
389
+ linked_domains=domain_resources,
390
+ )
391
+
392
+ return comm_service
393
+
394
+ def _add_webapp_vault(self, administrators: list[str], suffix: str) -> azure.keyvault.Vault:
395
+ # Create a keyvault with a random suffix to make the name unique
396
+ random_suffix = pulumi_random.RandomString(
397
+ f"vault-suffix-{suffix}",
398
+ # Total length is 24, so deduct the length of the suffix
399
+ length=(24 - 7 - len(suffix)),
400
+ special=False,
401
+ upper=False,
402
+ )
403
+
404
+ vault = azure.keyvault.Vault(
405
+ f"vault-{suffix}",
406
+ resource_group_name=self._rg,
407
+ vault_name=random_suffix.result.apply(lambda r: f"vault-{suffix}-{r}"),
408
+ properties=azure.keyvault.VaultPropertiesArgs(
409
+ tenant_id=self._tenant_id,
410
+ sku=azure.keyvault.SkuArgs(
411
+ name=azure.keyvault.SkuName.STANDARD,
412
+ family=azure.keyvault.SkuFamily.A,
413
+ ),
414
+ enable_rbac_authorization=True,
415
+ ),
416
+ )
417
+
418
+ # Find the Key Vault Administrator role
419
+ administrator_role = vault.id.apply(
420
+ lambda scope: azure.authorization.get_role_definition(
421
+ role_definition_id="00482a5a-887f-4fb3-b363-3b7fe8e74483",
422
+ scope=scope,
423
+ )
424
+ )
425
+
426
+ # Add vault administrators
427
+ for a in administrators:
428
+ azure.authorization.RoleAssignment(
429
+ f"ra-{suffix}-vault-admin-{a}",
430
+ principal_id=a,
431
+ principal_type=azure.authorization.PrincipalType.USER,
432
+ role_definition_id=administrator_role.id,
433
+ scope=vault.id,
434
+ )
435
+
436
+ return vault
437
+
438
+ def _add_webapp_secret(self, vault: azure.keyvault.Vault, secret_name: str, config_secret_name: str, suffix: str):
439
+ secret = self._config.require_secret(config_secret_name)
440
+
441
+ # Normalize the secret name
442
+ secret_name = secret_name.replace("_", "-").lower()
443
+
444
+ # Create a secret in the vault
445
+ return azure.keyvault.Secret(
446
+ f"secret-{suffix}-{secret_name}",
447
+ resource_group_name=self._rg,
448
+ vault_name=vault.name,
449
+ secret_name=secret_name,
450
+ properties=azure.keyvault.SecretPropertiesArgs(
451
+ value=secret,
452
+ ),
453
+ )
454
+
351
455
  def _get_storage_account_access_keys(
352
456
  self, storage_account: azure.storage.StorageAccount
353
457
  ) -> Sequence[azure.storage.outputs.StorageAccountKeyResponse]:
@@ -394,6 +498,10 @@ class DjangoDeployment(pulumi.ComponentResource):
394
498
  website_hosts: list[str],
395
499
  django_settings_module: str,
396
500
  environment_variables: dict[str, str] = {},
501
+ secrets: dict[str, str] = {},
502
+ comms_data_location: Optional[str] = None,
503
+ comms_domains: Optional[list[str]] = [],
504
+ vault_administrators: Optional[list[str]] = [],
397
505
  ) -> azure.web.WebApp:
398
506
  """
399
507
  Create a Django website with it's own database and storage containers.
@@ -405,6 +513,12 @@ class DjangoDeployment(pulumi.ComponentResource):
405
513
  :param website_hosts: The list of custom host names for the website.
406
514
  :param django_settings_module: The Django settings module to load.
407
515
  :param environment_variables: A dictionary of environment variables to set.
516
+ :param secrets: A dictionary of secrets to store in the Key Vault and assign as environment variables.
517
+ The key is the name of the Pulumi secret, the value is the name of the environment variable
518
+ and the name of the secret in the Key Vault.
519
+ :param comms_data_location: The data location for the Communication Services (optional if you don't need it).
520
+ :param comms_domains: The list of custom domains for the E-mail Communication Services (optional).
521
+ :param vault_administrator: The principal ID of the vault administrator (optional).
408
522
  """
409
523
 
410
524
  # Create a database
@@ -433,6 +547,22 @@ class DjangoDeployment(pulumi.ComponentResource):
433
547
  container_name=f"{name}-static",
434
548
  )
435
549
 
550
+ # Communication Services (optional)
551
+ if comms_data_location:
552
+ comms = self._add_webapp_comms(comms_data_location, comms_domains, f"{name}-{self._name}")
553
+ # Add the service endpoint as environment variable
554
+ environment_variables["AZURE_COMMUNICATION_SERVICE_ENDPOINT"] = comms.host_name.apply(lambda host: f"https://{host}")
555
+ else:
556
+ comms = None
557
+
558
+ # Key Vault
559
+ vault = self._add_webapp_vault(vault_administrators, f"{name}-{self._name}")
560
+
561
+ # Add secrets
562
+ for config_name, env_name in secrets.items():
563
+ s = self._add_webapp_secret(vault, env_name, config_name, f"{name}-{self._name}")
564
+ environment_variables[f"{env_name}_SECRET_NAME"] = s.name
565
+
436
566
  # Create a Django Secret Key (random)
437
567
  secret_key = pulumi_random.RandomString(f"django-secret-{name}-{self._name}", length=50)
438
568
 
@@ -455,6 +585,7 @@ class DjangoDeployment(pulumi.ComponentResource):
455
585
  ),
456
586
  https_only=True,
457
587
  site_config=azure.web.SiteConfigArgs(
588
+ app_command_line="cicd/startup.sh",
458
589
  always_on=True,
459
590
  health_check_path=self.HEALTH_CHECK_PATH,
460
591
  ftps_state=azure.web.FtpsState.DISABLED,
@@ -473,12 +604,16 @@ class DjangoDeployment(pulumi.ComponentResource):
473
604
  azure.web.NameValuePairArgs(name="DJANGO_SETTINGS_MODULE", value=django_settings_module),
474
605
  azure.web.NameValuePairArgs(name="DJANGO_SECRET_KEY", value=secret_key.result),
475
606
  azure.web.NameValuePairArgs(name="DJANGO_ALLOWED_HOSTS", value=",".join(website_hosts)),
607
+ # Vault settings
608
+ azure.web.NameValuePairArgs(name="AZURE_KEY_VAULT", value=vault.name),
476
609
  # Storage settings
477
610
  azure.web.NameValuePairArgs(name="AZURE_STORAGE_ACCOUNT_NAME", value=self._storage_account.name),
478
611
  azure.web.NameValuePairArgs(name="AZURE_STORAGE_CONTAINER_STATICFILES", value=static_container.name),
479
612
  azure.web.NameValuePairArgs(name="AZURE_STORAGE_CONTAINER_MEDIA", value=media_container.name),
480
613
  # CDN
481
614
  azure.web.NameValuePairArgs(name="CDN_HOST", value=self._cdn_host),
615
+ azure.web.NameValuePairArgs(name="CDN_PROFILE", value=self._cdn_profile.name),
616
+ azure.web.NameValuePairArgs(name="CDN_ENDPOINT", value=self._cdn_endpoint.name),
482
617
  # Database settings
483
618
  azure.web.NameValuePairArgs(name="DB_HOST", value=self._pgsql.fully_qualified_domain_name),
484
619
  azure.web.NameValuePairArgs(name="DB_NAME", value=db.name),
@@ -517,6 +652,24 @@ class DjangoDeployment(pulumi.ComponentResource):
517
652
  f"{name}_deploy_ssh_key_url", app.name.apply(lambda name: f"https://{name}.scm.azurewebsites.net/api/sshkey?ensurePublicKey=1")
518
653
  )
519
654
 
655
+ # Find the role for Key Vault Secrets User
656
+ vault_access_role = vault.id.apply(
657
+ lambda scope: azure.authorization.get_role_definition(
658
+ role_definition_id="4633458b-17de-408a-b874-0445c86b69e6",
659
+ scope=scope,
660
+ )
661
+ )
662
+
663
+ # Grant the app access to the vault
664
+ azure.authorization.RoleAssignment(
665
+ f"ra-{name}-vault-user",
666
+ principal_id=principal_id,
667
+ principal_type=azure.authorization.PrincipalType.SERVICE_PRINCIPAL,
668
+ # Key Vault Secrets User
669
+ role_definition_id=vault_access_role.id,
670
+ scope=vault.id,
671
+ )
672
+
520
673
  # Find the role for Storage Blob Data Contributor
521
674
  storage_role = self._storage_account.id.apply(
522
675
  lambda scope: azure.authorization.get_role_definition(
@@ -534,6 +687,40 @@ class DjangoDeployment(pulumi.ComponentResource):
534
687
  scope=self._storage_account.id,
535
688
  )
536
689
 
690
+ # Grant the app to send e-mails
691
+ if comms:
692
+ comms_role = comms.id.apply(
693
+ lambda scope: azure.authorization.get_role_definition(
694
+ # Contributor
695
+ role_definition_id="b24988ac-6180-42a0-ab88-20f7382dd24c",
696
+ scope=scope,
697
+ )
698
+ )
699
+
700
+ azure.authorization.RoleAssignment(
701
+ f"ra-{name}-comms",
702
+ principal_id=principal_id,
703
+ principal_type=azure.authorization.PrincipalType.SERVICE_PRINCIPAL,
704
+ role_definition_id=comms_role.id,
705
+ scope=comms.id,
706
+ )
707
+
708
+ # Grant the app to purge the CDN endpoint
709
+ cdn_role = self._cdn_endpoint.id.apply(
710
+ lambda scope: azure.authorization.get_role_definition(
711
+ role_definition_id="/426e0c7f-0c7e-4658-b36f-ff54d6c29b45",
712
+ scope=scope,
713
+ )
714
+ )
715
+
716
+ azure.authorization.RoleAssignment(
717
+ f"ra-{name}-cdn",
718
+ principal_id=principal_id,
719
+ principal_type=azure.authorization.PrincipalType.SERVICE_PRINCIPAL,
720
+ role_definition_id=cdn_role.id,
721
+ scope=self._cdn_endpoint.id,
722
+ )
723
+
537
724
  # Create a CORS rules for this website
538
725
  if website_hosts:
539
726
  origins = [f"https://{host}" for host in website_hosts]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pulumi-django-azure
3
- Version: 1.0.4
3
+ Version: 1.0.12
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
@@ -45,9 +45,24 @@ To have a proper and secure environment, we need these components:
45
45
  * Storage account for media and static files
46
46
  * CDN endpoint in front with a domain name of our choosing
47
47
  * PostgreSQL server
48
+ * Azure Communication Services to send e-mails
48
49
  * Webapp with multiple custom host names and managed SSL for the website itself
50
+ * Azure Key Vault per application
49
51
  * Webapp running pgAdmin
50
52
 
53
+ ## Project requirements
54
+ Your Django project should contain a folder `cicd` with these files:
55
+ * pre_build.sh: commands to be executed before building the application, for example NPM install, CSS build commands,...
56
+ * post_build.sh: commands to be executed after building the application, e.g. cleaning up.
57
+ Note that this runs in the identity of the build container, so you should not run database or storage manipulations here.
58
+ * startup.sh: commands to run the actual application. I recommend to put at least:
59
+ ```bash
60
+ python manage.py migrate
61
+ python manage.py collectstatic --noinput
62
+ python manage.py purge_cdn
63
+ gunicorn --timeout 600 --workers $((($NUM_CORES*2)+1)) --chdir $APP_PATH yourapplication.wsgi --access-logfile '-' --error-logfile '-'
64
+ ```
65
+ Be sure to change `yourapplication` in the above. To see the `purge_cdn` management command we use here, see below.
51
66
  ## Installation
52
67
  This package is published on PyPi, so you can just add pulumi-django-azure to your requirements file.
53
68
 
@@ -101,6 +116,8 @@ django.add_django_website(
101
116
  repository_branch="main",
102
117
  website_hosts=["example.com", "www.example.com"],
103
118
  django_settings_module="mywebsite.settings.production",
119
+ comms_data_location="europe",
120
+ comms_domains=["mydomain.com"],
104
121
  )
105
122
 
106
123
  django.add_database_administrator(
@@ -120,6 +137,7 @@ django.add_database_administrator(
120
137
  6. Re-deploy with custom hosts
121
138
  7. Re-deploy once more to enable HTTPS on website domains
122
139
  8. Manually activate HTTPS on the CDN host
140
+ 9. Go to the e-mail communications service on Azure and configure DKIM, SPF,... for your custom domains.
123
141
 
124
142
  ## Custom domain name for CDN
125
143
  When deploying the first time, you will get a `cdn_cname` output. You need to create a CNAME to this domain before the deployment of the custom domain will succeed.
@@ -172,6 +190,55 @@ pgAdmin will be created with a default login:
172
190
 
173
191
  Best practice is to log in right away, create a user for yourself and delete this default user.
174
192
 
193
+ ## Azure OAuth2 / Django Social Auth
194
+ If you want to set up login with Azure, which would make sense since you are in the ecosystem, you need to create an App Registration in Entra ID, create a secret and then register these settings in your stack:
195
+ ```
196
+ pulumi config set --secret --path 'mywebsite_social_auth_azure.key' secret_ID
197
+ pulumi config set --secret --path 'mywebsite_social_auth_azure.secret' secret_value
198
+ pulumi config set --secret --path 'mywebsite_social_auth_azure.tenant_id' directory_tenant_id
199
+ pulumi config set --secret --path 'mywebsite_social_auth_azure.client_id' application_id
200
+ ```
201
+
202
+ Then in your Django deployment, pass to the `add_django_website` command:
203
+ ```
204
+ secrets={
205
+ "mywebsite_social_auth_azure": "AZURE_OAUTH",
206
+ },
207
+ ```
208
+
209
+ The value will be automatically stored in the vault where the application has access to.
210
+ The environment variable will be suffixed with `_SECRET_NAME`.
211
+
212
+ Then, in your application, retrieve this data from the vault, e.g.:
213
+ ```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
+ # Social Auth settings
226
+ oauth_secret = azure_key_vault_client.get_secret(env("AZURE_OAUTH_SECRET_NAME"))
227
+ oauth_secret = json.loads(oauth_secret.value)
228
+ SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY = oauth_secret["client_id"]
229
+ SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET = oauth_secret["secret"]
230
+ SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID = oauth_secret["tenant_id"]
231
+ SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ["username", "first_name", "last_name", "email"]
232
+ SOCIAL_AUTH_POSTGRES_JSONFIELD = True
233
+
234
+ AUTHENTICATION_BACKENDS = (
235
+ "social_core.backends.azuread_tenant.AzureADTenantOAuth2",
236
+ "django.contrib.auth.backends.ModelBackend",
237
+ )
238
+ ```
239
+
240
+ And of course add the login button somewhere, following Django Social Auth instructions.
241
+
175
242
  ## Automate deployments
176
243
  When using a service like GitLab, you can configure a Webhook to fire upon a push to your branch.
177
244
 
@@ -190,6 +257,82 @@ Be sure to configure the SSH key that Azure will use on GitLab side. You can obt
190
257
 
191
258
  This would then trigger a redeploy everytime you make a commit to your live branch.
192
259
 
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
+
193
336
  ## Change requests
194
337
  I created this for internal use but since it took me a while to puzzle all the things together I decided to share it.
195
338
  Therefore this project is not super generic, but tailored to my needs. I am however open to pull or change requests to improve this project or to make it more usable for others.
@@ -0,0 +1,7 @@
1
+ pulumi_django_azure/__init__.py,sha256=tXTvPfr8-Nll5cjMyY9yj_0z_PQ0XAcxihzHRCES-hU,63
2
+ pulumi_django_azure/django_deployment.py,sha256=MBdNH2iEOoNyWY5mxiLy8iUkW514LsG-wNjo4MVZlqY,31479
3
+ pulumi_django_azure-1.0.12.dist-info/LICENSE,sha256=NX2LN3U319Zaac8b7ZgfNOco_nTBbN531X_M_13niSg,1087
4
+ pulumi_django_azure-1.0.12.dist-info/METADATA,sha256=hPKNy_NeqAhvQGJFn-4Zw8AUiWU6pM0jpS9ypjzX2as,13957
5
+ pulumi_django_azure-1.0.12.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ pulumi_django_azure-1.0.12.dist-info/top_level.txt,sha256=MNPRJhq-_G8EMCHRkjdcb_xrqzOkmKogXUGV7Ysz3g0,20
7
+ pulumi_django_azure-1.0.12.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,7 +0,0 @@
1
- pulumi_django_azure/__init__.py,sha256=tXTvPfr8-Nll5cjMyY9yj_0z_PQ0XAcxihzHRCES-hU,63
2
- pulumi_django_azure/django_deployment.py,sha256=yvpOUkcHs37Mb2xTD6BqtB21opxTvFLQzRxMHXTK2aY,23410
3
- pulumi_django_azure-1.0.4.dist-info/LICENSE,sha256=NX2LN3U319Zaac8b7ZgfNOco_nTBbN531X_M_13niSg,1087
4
- pulumi_django_azure-1.0.4.dist-info/METADATA,sha256=25RH8tZL2LrhBjuYRngUcRArER9TiAorLEaCJulFhLA,7887
5
- pulumi_django_azure-1.0.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
- pulumi_django_azure-1.0.4.dist-info/top_level.txt,sha256=MNPRJhq-_G8EMCHRkjdcb_xrqzOkmKogXUGV7Ysz3g0,20
7
- pulumi_django_azure-1.0.4.dist-info/RECORD,,