pulumi-django-azure 1.0.28__py3-none-any.whl → 1.0.59__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.
@@ -1,9 +1,12 @@
1
1
  from collections.abc import Sequence
2
2
 
3
3
  import pulumi
4
+ import pulumi_azure as azure_classic
4
5
  import pulumi_azure_native as azure
5
6
  import pulumi_random
6
7
 
8
+ REDIS_IMAGE = "mcr.microsoft.com/mirror/docker/library/redis:7.2"
9
+
7
10
 
8
11
  class HostDefinition:
9
12
  """
@@ -68,11 +71,10 @@ class DjangoDeployment(pulumi.ComponentResource):
68
71
  app_service_sku: azure.web.SkuDescriptionArgs,
69
72
  storage_account_name: str,
70
73
  storage_allowed_origins: Sequence[str] | None = None,
74
+ pgsql_version: str = "17",
71
75
  pgsql_parameters: dict[str, str] | None = None,
72
76
  pgadmin_access_ip: Sequence[str] | None = None,
73
77
  pgadmin_dns_zone: azure.dns.Zone | None = None,
74
- cache_ip_prefix: str | None = None,
75
- cache_sku: azure.redis.SkuArgs | None = None,
76
78
  cdn_host: HostDefinition | None = None,
77
79
  opts=None,
78
80
  ):
@@ -92,8 +94,6 @@ class DjangoDeployment(pulumi.ComponentResource):
92
94
  :param pgsql_parameters: The parameters to set on the PostgreSQL server. (optional)
93
95
  :param pgadmin_access_ip: The IP addresses to allow access to pgAdmin. If empty, all IP addresses are allowed.
94
96
  :param pgadmin_dns_zone: The Azure DNS zone to a pgadmin DNS record in. (optional)
95
- :param cache_ip_prefix: The IP prefix for the cache subnet. (optional)
96
- :param cache_sku: The SKU for the cache. (optional)
97
97
  :param cdn_host: A custom CDN host name. (optional)
98
98
  :param opts: The resource options
99
99
  """
@@ -113,13 +113,7 @@ class DjangoDeployment(pulumi.ComponentResource):
113
113
  self._cdn_host = self._create_cdn(custom_host=cdn_host)
114
114
 
115
115
  # PostgreSQL resources
116
- self._create_database(sku=pgsql_sku, ip_prefix=pgsql_ip_prefix, parameters=pgsql_parameters)
117
-
118
- # Cache resources
119
- if cache_ip_prefix and cache_sku:
120
- self._create_cache(sku=cache_sku, ip_prefix=cache_ip_prefix)
121
- else:
122
- self._cache = None
116
+ self._create_database(sku=pgsql_sku, version=pgsql_version, ip_prefix=pgsql_ip_prefix, parameters=pgsql_parameters)
123
117
 
124
118
  # Subnet for the apps
125
119
  self._app_subnet = self._create_subnet(
@@ -185,7 +179,7 @@ class DjangoDeployment(pulumi.ComponentResource):
185
179
  resource_group_name=self._rg,
186
180
  location="global",
187
181
  sku=azure.cdn.SkuArgs(
188
- name=azure.cdn.SkuName.STANDARD_MICROSOFT,
182
+ name=azure.cdn.SkuName.STANDARD_AZURE_FRONT_DOOR,
189
183
  ),
190
184
  )
191
185
 
@@ -193,73 +187,86 @@ class DjangoDeployment(pulumi.ComponentResource):
193
187
  lambda primary_endpoints: primary_endpoints.blob.replace("https://", "").replace("/", "")
194
188
  )
195
189
 
196
- self._cdn_endpoint = azure.cdn.Endpoint(
190
+ self._cdn_endpoint = azure.cdn.AFDEndpoint(
197
191
  f"cdn-endpoint-{self._name}",
198
192
  resource_group_name=self._rg,
199
193
  location="global",
200
- is_compression_enabled=True,
201
- content_types_to_compress=[
202
- "application/javascript",
203
- "application/json",
204
- "application/x-javascript",
205
- "application/xml",
206
- "text/css",
207
- "text/html",
208
- "text/javascript",
209
- "text/plain",
210
- ],
211
- is_http_allowed=False,
212
- is_https_allowed=True,
213
194
  profile_name=self._cdn_profile.name,
214
- origin_host_header=endpoint_origin,
215
- origins=[azure.cdn.DeepCreatedOriginArgs(name="origin-storage", host_name=endpoint_origin)],
216
- query_string_caching_behavior=azure.cdn.QueryStringCachingBehavior.USE_QUERY_STRING,
217
195
  )
218
196
 
219
- pulumi.export("cdn_cname", self._cdn_endpoint.host_name)
197
+ origin_group = azure.cdn.AFDOriginGroup(
198
+ f"cdn-origin-group-{self._name}",
199
+ resource_group_name=self._rg,
200
+ profile_name=self._cdn_profile.name,
201
+ load_balancing_settings=azure.cdn.LoadBalancingSettingsParametersArgs(
202
+ sample_size=4, successful_samples_required=3, additional_latency_in_milliseconds=50
203
+ ),
204
+ )
205
+
206
+ azure.cdn.AFDOrigin(
207
+ f"cdn-origin-{self._name}",
208
+ resource_group_name=self._rg,
209
+ profile_name=self._cdn_profile.name,
210
+ origin_group_name=origin_group.name,
211
+ host_name=endpoint_origin,
212
+ origin_host_header=endpoint_origin,
213
+ )
220
214
 
221
- # Add custom domain if given
222
215
  if custom_host:
223
- if custom_host.zone:
224
- # Create a DNS record for the custom host in the given zone
225
- rs = azure.dns.RecordSet(
226
- f"cdn-cname-{self._name}",
227
- resource_group_name=self._rg,
228
- zone_name=custom_host.zone.name,
229
- relative_record_set_name=custom_host.host,
230
- record_type="CNAME",
231
- ttl=3600,
232
- target_resource=azure.dns.SubResourceArgs(
233
- id=self._cdn_endpoint.id,
234
- ),
235
- )
216
+ # Add custom hostname
217
+ custom_domain = azure.cdn.AFDCustomDomain(
218
+ f"cdn-custom-domain-{self._name}",
219
+ resource_group_name=self._rg,
220
+ profile_name=self._cdn_profile.name,
221
+ host_name=custom_host.host,
222
+ )
223
+ custom_domains = [azure.cdn.ResourceReferenceArgs(id=custom_domain.id)]
236
224
 
237
- azure.cdn.CustomDomain(
238
- f"cdn-custom-domain-{self._name}",
239
- resource_group_name=self._rg,
240
- profile_name=self._cdn_profile.name,
241
- endpoint_name=self._cdn_endpoint.name,
242
- host_name=custom_host.full_host,
243
- opts=pulumi.ResourceOptions(depends_on=rs),
244
- )
225
+ # Export the TXT validation records needed
226
+ pulumi.export("cdn_validation_record_txt_name", f"_dnsauth.{custom_host.host}")
227
+ pulumi.export(
228
+ "cdn_validation_record_txt_value", custom_domain.validation_properties.apply(lambda properties: properties.validation_token)
229
+ )
230
+ else:
231
+ custom_domains = None
245
232
 
246
- return custom_host.full_host
247
- else:
248
- # Add custom hostname without a zone
249
- azure.cdn.CustomDomain(
250
- f"cdn-custom-domain-{self._name}",
251
- resource_group_name=self._rg,
252
- profile_name=self._cdn_profile.name,
253
- endpoint_name=self._cdn_endpoint.name,
254
- host_name=custom_host.host,
255
- )
233
+ azure.cdn.Route(
234
+ f"cdn-route-{self._name}",
235
+ resource_group_name=self._rg,
236
+ profile_name=self._cdn_profile.name,
237
+ endpoint_name=self._cdn_endpoint.name,
238
+ origin_group=azure.cdn.ResourceReferenceArgs(id=origin_group.id),
239
+ link_to_default_domain=azure.cdn.LinkToDefaultDomain.ENABLED,
240
+ custom_domains=custom_domains,
241
+ cache_configuration=azure.cdn.AfdRouteCacheConfigurationArgs(
242
+ compression_settings=azure.cdn.CompressionSettingsArgs(
243
+ content_types_to_compress=[
244
+ "application/javascript",
245
+ "application/json",
246
+ "application/x-javascript",
247
+ "application/xml",
248
+ "text/css",
249
+ "text/html",
250
+ "text/javascript",
251
+ "text/plain",
252
+ ],
253
+ is_compression_enabled=True,
254
+ ),
255
+ query_string_caching_behavior=azure.cdn.AfdQueryStringCachingBehavior.USE_QUERY_STRING,
256
+ ),
257
+ supported_protocols=[azure.cdn.AFDEndpointProtocols.HTTP, azure.cdn.AFDEndpointProtocols.HTTPS],
258
+ https_redirect=azure.cdn.HttpsRedirect.ENABLED,
259
+ opts=pulumi.ResourceOptions(depends_on=[origin_group]),
260
+ )
261
+
262
+ pulumi.export("cdn_cname", self._cdn_endpoint.host_name)
256
263
 
257
- return custom_host.host
264
+ if custom_host:
265
+ return custom_domain.host_name
258
266
  else:
259
- # Return the default CDN host name
260
267
  return self._cdn_endpoint.host_name
261
268
 
262
- def _create_database(self, sku: azure.dbforpostgresql.SkuArgs, ip_prefix: str, parameters: dict[str, str]):
269
+ def _create_database(self, sku: azure.dbforpostgresql.SkuArgs, version: str, ip_prefix: str, parameters: dict[str, str]):
263
270
  # Create subnet for PostgreSQL
264
271
  subnet = self._create_subnet(
265
272
  name="pgsql",
@@ -291,13 +298,16 @@ class DjangoDeployment(pulumi.ComponentResource):
291
298
  f"pgsql-{self._name}",
292
299
  resource_group_name=self._rg,
293
300
  sku=sku,
294
- version="16",
301
+ version=version,
295
302
  auth_config=azure.dbforpostgresql.AuthConfigArgs(
296
303
  password_auth=azure.dbforpostgresql.PasswordAuthEnum.DISABLED,
297
304
  active_directory_auth=azure.dbforpostgresql.ActiveDirectoryAuthEnum.ENABLED,
298
305
  tenant_id=self._tenant_id,
299
306
  ),
300
- storage=azure.dbforpostgresql.StorageArgs(storage_size_gb=32),
307
+ storage=azure.dbforpostgresql.StorageArgs(
308
+ storage_size_gb=32,
309
+ auto_grow=azure.dbforpostgresql.StorageAutoGrow.ENABLED,
310
+ ),
301
311
  network=azure.dbforpostgresql.NetworkArgs(
302
312
  delegated_subnet_resource_id=subnet.id,
303
313
  private_dns_zone_arm_resource_id=dns.id,
@@ -322,85 +332,6 @@ class DjangoDeployment(pulumi.ComponentResource):
322
332
 
323
333
  pulumi.export("pgsql_host", self._pgsql.fully_qualified_domain_name)
324
334
 
325
- def _create_cache(self, sku: azure.redis.SkuArgs, ip_prefix: str):
326
- # Create a Redis cache
327
- self._cache = azure.redis.Redis(
328
- f"cache-{self._name}",
329
- resource_group_name=self._rg,
330
- sku=sku,
331
- enable_non_ssl_port=False,
332
- public_network_access=azure.redis.PublicNetworkAccess.DISABLED,
333
- )
334
-
335
- # Create an access policy that gives us access to the cache
336
- self._cache_access_policy = azure.redis.AccessPolicy(
337
- f"cache-access-policy-{self._name}",
338
- resource_group_name=self._rg,
339
- cache_name=self._cache.name,
340
- # Same as the built in Data Contributor policy + flushdb permissions
341
- permissions="+@all -@dangerous +flushdb +cluster|info +cluster|nodes +cluster|slots allkeys",
342
- )
343
-
344
- # Allocate a subnet for the cache
345
- subnet = self._create_subnet(
346
- name="cache",
347
- prefix=ip_prefix,
348
- )
349
-
350
- # Create a private DNS zone for the cache
351
- dns = azure.privatedns.PrivateZone(
352
- f"dns-cache-{self._name}",
353
- resource_group_name=self._rg,
354
- location="global",
355
- private_zone_name="privatelink.redis.cache.windows.net",
356
- )
357
-
358
- # Link the private DNS zone to the VNet in order to make resolving work
359
- azure.privatedns.VirtualNetworkLink(
360
- f"vnet-link-cache-{self._name}",
361
- resource_group_name=self._rg,
362
- location="global",
363
- private_zone_name=dns.name,
364
- virtual_network=azure.network.SubResourceArgs(id=self._vnet.id),
365
- registration_enabled=True,
366
- )
367
-
368
- # Create a private endpoint for the cache
369
- endpoint = azure.network.PrivateEndpoint(
370
- f"private-endpoint-cache-{self._name}",
371
- resource_group_name=self._rg,
372
- subnet=azure.network.SubnetArgs(id=subnet.id),
373
- custom_network_interface_name=f"nic-cache-{self._name}",
374
- private_link_service_connections=[
375
- azure.network.PrivateLinkServiceConnectionArgs(
376
- name=f"pls-cache-{self._name}",
377
- private_link_service_id=self._cache.id,
378
- group_ids=["redisCache"],
379
- )
380
- ],
381
- )
382
-
383
- # Get the private IP address of the endpoint NIC
384
- ip = endpoint.network_interfaces.apply(
385
- lambda nics: azure.network.get_network_interface(
386
- network_interface_name=nics[0].id.split("/")[-1],
387
- resource_group_name=self._rg,
388
- )
389
- .ip_configurations[0]
390
- .private_ip_address
391
- )
392
-
393
- # Create a DNS record for the cache
394
- azure.privatedns.PrivateRecordSet(
395
- f"dns-a-cache-{self._name}",
396
- resource_group_name=self._rg,
397
- private_zone_name=dns.name,
398
- relative_record_set_name=self._cache.name,
399
- record_type="A",
400
- ttl=300,
401
- a_records=[azure.privatedns.ARecordArgs(ipv4_address=ip)],
402
- )
403
-
404
335
  def _create_subnet(
405
336
  self,
406
337
  name,
@@ -562,16 +493,6 @@ class DjangoDeployment(pulumi.ComponentResource):
562
493
  },
563
494
  )
564
495
 
565
- def _get_existing_web_app_host_name_binding(self, resource_group_name: str, app_name: str, host_name: str):
566
- try:
567
- return azure.web.get_web_app_host_name_binding(
568
- resource_group_name=resource_group_name,
569
- name=app_name,
570
- host_name=host_name,
571
- )
572
- except Exception:
573
- return None
574
-
575
496
  def _add_webapp_host(
576
497
  self,
577
498
  app: azure.web.WebApp,
@@ -579,13 +500,9 @@ class DjangoDeployment(pulumi.ComponentResource):
579
500
  suffix: str,
580
501
  identifier: str,
581
502
  depends_on: Sequence[pulumi.Resource] | None = None,
582
- ):
503
+ ) -> azure.web.WebAppHostNameBinding:
583
504
  """
584
- Because of a circular dependency, we need to create the certificate and the binding in two steps.
585
- First we create a binding without a certificate,
586
- then we create the certificate and then we update the binding.
587
-
588
- The certificate needs the binding, and the binding needs the thumbprint of the certificate.
505
+ We need to use a few steps and CertificateBinding from Azure Classic to make this work.
589
506
 
590
507
  See also: https://github.com/pulumi/pulumi-azure-native/issues/578
591
508
 
@@ -598,52 +515,37 @@ class DjangoDeployment(pulumi.ComponentResource):
598
515
  if not depends_on:
599
516
  depends_on = []
600
517
 
601
- # Retrieve the existing binding (None if it doesn't exist)
602
- existing_binding = pulumi.Output.all(app.resource_group, app.name, host).apply(
603
- lambda args: self._get_existing_web_app_host_name_binding(
604
- resource_group_name=args[0],
605
- app_name=args[1],
606
- host_name=args[2],
607
- )
518
+ # Create a binding without a certificate
519
+ binding = azure.web.WebAppHostNameBinding(
520
+ f"host-binding-{suffix}-{identifier}",
521
+ resource_group_name=self._rg,
522
+ name=app.name,
523
+ host_name=host,
524
+ ssl_state=azure.web.SslState.DISABLED,
525
+ # Ignore changes in SSL state and thumbprint
526
+ opts=pulumi.ResourceOptions(depends_on=depends_on, ignore_changes=["ssl_state", "thumbprint"]),
608
527
  )
609
528
 
610
- # Create an inline function that we will invoke through the Output.apply lambda
611
- def _create_binding_with_cert(existing_binding):
612
- if existing_binding:
613
- # Create a managed certificate
614
- # This will work because the binding exists actually
615
- certificate = azure.web.Certificate(
616
- f"cert-{suffix}-{identifier}",
617
- resource_group_name=self._rg,
618
- server_farm_id=app.server_farm_id,
619
- canonical_name=host,
620
- host_names=[host],
621
- opts=pulumi.ResourceOptions(depends_on=depends_on),
622
- )
623
-
624
- # Create a new binding, replacing the old one,
625
- # with the certificate
626
- azure.web.WebAppHostNameBinding(
627
- f"host-binding-{suffix}-{identifier}",
628
- resource_group_name=self._rg,
629
- name=app.name,
630
- host_name=host,
631
- ssl_state=azure.web.SslState.SNI_ENABLED,
632
- thumbprint=certificate.thumbprint,
633
- opts=pulumi.ResourceOptions(depends_on=depends_on),
634
- )
529
+ # Create a certificate that depends on the binding
530
+ certificate = azure.web.Certificate(
531
+ f"cert-{suffix}-{identifier}",
532
+ resource_group_name=self._rg,
533
+ server_farm_id=app.server_farm_id,
534
+ canonical_name=host,
535
+ opts=pulumi.ResourceOptions(depends_on=[binding] + depends_on),
536
+ )
635
537
 
636
- else:
637
- # Create a binding without a certificate
638
- azure.web.WebAppHostNameBinding(
639
- f"host-binding-{suffix}-{identifier}",
640
- resource_group_name=self._rg,
641
- name=app.name,
642
- host_name=host,
643
- opts=pulumi.ResourceOptions(depends_on=depends_on),
644
- )
538
+ # Create CertificateBinding from Azure Classic to link it together
539
+ # Inspired by https://github.com/pulumi/pulumi-azure-native/issues/578#issuecomment-2705952672
540
+ azure_classic.appservice.CertificateBinding(
541
+ f"cert-binding-{suffix}-{identifier}",
542
+ hostname_binding_id=binding.id,
543
+ certificate_id=certificate.id,
544
+ ssl_state="SniEnabled",
545
+ opts=pulumi.ResourceOptions(depends_on=depends_on, ignore_changes=["hostname_binding_id"]),
546
+ )
645
547
 
646
- existing_binding.apply(_create_binding_with_cert)
548
+ return binding
647
549
 
648
550
  def _create_comms_dns_records(self, suffix, host: HostDefinition, records: dict) -> list[azure.dns.RecordSet]:
649
551
  created_records = []
@@ -850,15 +752,17 @@ class DjangoDeployment(pulumi.ComponentResource):
850
752
  repository_branch: str,
851
753
  website_hosts: list[HostDefinition],
852
754
  django_settings_module: str,
853
- python_version: str = "3.13",
755
+ python_version: str = "3.14",
854
756
  environment_variables: dict[str, str] | None = None,
855
757
  secrets: dict[str, str] | None = None,
856
758
  comms_data_location: str | None = None,
857
759
  comms_domains: list[HostDefinition] | None = None,
858
760
  dedicated_app_service_sku: azure.web.SkuDescriptionArgs | None = None,
859
761
  vault_administrators: list[str] | None = None,
860
- cache_db: int | None = None,
861
- startup_timeout: int = 300,
762
+ redis_sidecar: bool = True,
763
+ django_tasks: bool = True,
764
+ startup_timeout: int = 600,
765
+ log_retention_days: int = 7,
862
766
  ) -> azure.web.WebApp:
863
767
  """
864
768
  Create a Django website with it's own database and storage containers.
@@ -878,7 +782,7 @@ class DjangoDeployment(pulumi.ComponentResource):
878
782
  :param comms_domains: The list of custom domains for the E-mail Communication Services (optional).
879
783
  :param dedicated_app_service_sku: The SKU for the dedicated App Service Plan (optional).
880
784
  :param vault_administrators: The principal IDs of the vault administrators (optional).
881
- :param cache_db: The index of the cache database to use (optional).
785
+ :param redis_sidecar: Whether to create a Redis sidecar container.
882
786
  :param startup_timeout: The startup timeout for the App Service (default is 300 seconds).
883
787
  """
884
788
 
@@ -908,6 +812,16 @@ class DjangoDeployment(pulumi.ComponentResource):
908
812
  container_name=f"{name}-static",
909
813
  )
910
814
 
815
+ # Redis cache environment variable
816
+ if redis_sidecar:
817
+ environment_variables["REDIS_SIDECAR"] = "true"
818
+
819
+ if django_tasks:
820
+ if not redis_sidecar:
821
+ raise ValueError("django-tasks requires redis-sidecar to be enabled.")
822
+
823
+ environment_variables["DJANGO_TASKS"] = "true"
824
+
911
825
  # Communication Services (optional)
912
826
  if comms_data_location:
913
827
  if not comms_domains:
@@ -928,15 +842,12 @@ class DjangoDeployment(pulumi.ComponentResource):
928
842
  s = self._add_webapp_secret(vault, env_name, config_name, f"{name}-{self._name}")
929
843
  environment_variables[f"{env_name}_SECRET_NAME"] = s.name
930
844
 
931
- # Cache (we need to check explicitly if it is not None because the db could also be 0)
932
- if self._cache and cache_db is not None:
933
- environment_variables["REDIS_CACHE_HOST"] = self._cache.host_name
934
- environment_variables["REDIS_CACHE_PORT"] = self._cache.ssl_port.apply(lambda port: str(port))
935
- environment_variables["REDIS_CACHE_DB"] = str(cache_db)
936
-
937
845
  # Create a Django Secret Key (random)
938
846
  secret_key = pulumi_random.RandomString(f"django-secret-{name}-{self._name}", length=50)
939
847
 
848
+ if log_retention_days > 0:
849
+ environment_variables["WEBSITE_HTTPLOGGING_RETENTION_DAYS"] = str(log_retention_days)
850
+
940
851
  # Convert environment variables to NameValuePairArgs
941
852
  environment_variables = [
942
853
  azure.web.NameValuePairArgs(
@@ -977,6 +888,7 @@ class DjangoDeployment(pulumi.ComponentResource):
977
888
  python_version=python_version,
978
889
  # scm_type=azure.web.ScmType.EXTERNAL_GIT,
979
890
  linux_fx_version=f"PYTHON|{python_version}",
891
+ http20_enabled=True,
980
892
  app_settings=[
981
893
  # Startup settings
982
894
  azure.web.NameValuePairArgs(name="WEBSITES_CONTAINER_START_TIME_LIMIT", value=str(startup_timeout)),
@@ -984,7 +896,9 @@ class DjangoDeployment(pulumi.ComponentResource):
984
896
  azure.web.NameValuePairArgs(name="IS_AZURE_ENVIRONMENT", value="true"),
985
897
  # Build settings
986
898
  azure.web.NameValuePairArgs(name="SCM_DO_BUILD_DURING_DEPLOYMENT", value="true"),
987
- azure.web.NameValuePairArgs(name="PRE_BUILD_COMMAND", value="cicd/pre_build.sh"),
899
+ azure.web.NameValuePairArgs(name="PRE_BUILD_COMMAND", value="curl -sSL https://bootstrap.django-azu.re | bash"),
900
+ # azure.web.NameValuePairArgs(name="PRE_BUILD_COMMAND", value="cicd/pre_build.sh"),
901
+ # This script will be created by the bootstrap script
988
902
  azure.web.NameValuePairArgs(name="POST_BUILD_COMMAND", value="cicd/post_build.sh"),
989
903
  azure.web.NameValuePairArgs(name="DISABLE_COLLECTSTATIC", value="true"),
990
904
  azure.web.NameValuePairArgs(name="HEALTH_CHECK_PATH", value=self.HEALTH_CHECK_PATH),
@@ -1012,6 +926,18 @@ class DjangoDeployment(pulumi.ComponentResource):
1012
926
  ),
1013
927
  )
1014
928
 
929
+ # Redis cache
930
+ if redis_sidecar:
931
+ azure.web.WebAppSiteContainer(
932
+ f"redis-sidecar-{name}-{self._name}",
933
+ resource_group_name=self._rg,
934
+ name=app.name,
935
+ is_main=False,
936
+ container_name="redis-sidecar",
937
+ image=REDIS_IMAGE,
938
+ # target_port="6379",
939
+ )
940
+
1015
941
  # We need this to create the database role and grant permissions
1016
942
  principal_id = app.identity.apply(lambda identity: identity.principal_id)
1017
943
  pulumi.export(f"{name}_site_principal_id", principal_id)
@@ -1034,8 +960,10 @@ class DjangoDeployment(pulumi.ComponentResource):
1034
960
 
1035
961
  pulumi.export(f"{name}_deploy_url", pulumi.Output.concat(credentials.scm_uri, "/deploy"))
1036
962
 
963
+ bindings = []
1037
964
  for host in website_hosts:
1038
- dependencies = []
965
+ # Only one binding can be created at a time, so we need to depend on the previous one.
966
+ dependencies = bindings or []
1039
967
 
1040
968
  if host.zone:
1041
969
  # Create a DNS record in the zone.
@@ -1076,7 +1004,7 @@ class DjangoDeployment(pulumi.ComponentResource):
1076
1004
  dependencies.append(txt_validation)
1077
1005
 
1078
1006
  # Add the host with optional dependencies
1079
- self._add_webapp_host(
1007
+ binding = self._add_webapp_host(
1080
1008
  app=app,
1081
1009
  host=host.full_host,
1082
1010
  suffix=f"{name}-{self._name}",
@@ -1084,6 +1012,8 @@ class DjangoDeployment(pulumi.ComponentResource):
1084
1012
  depends_on=dependencies,
1085
1013
  )
1086
1014
 
1015
+ bindings.append(binding)
1016
+
1087
1017
  # To enable deployment from GitLab
1088
1018
  azure.web.WebAppSourceControl(
1089
1019
  f"app-{name}-sourcecontrol-{self._name}",
@@ -1137,18 +1067,6 @@ class DjangoDeployment(pulumi.ComponentResource):
1137
1067
  scope=self._storage_account.id,
1138
1068
  )
1139
1069
 
1140
- # Grant the app access to the cache if needed.
1141
- # We need to check explicitly if it is not None because the db could also be 0.
1142
- if self._cache and cache_db is not None:
1143
- azure.redis.AccessPolicyAssignment(
1144
- f"ra-{name}-cache",
1145
- resource_group_name=self._rg,
1146
- cache_name=self._cache.name,
1147
- object_id=principal_id,
1148
- object_id_alias=f"app-{name}-managed-identity",
1149
- access_policy_name=self._cache_access_policy.name,
1150
- )
1151
-
1152
1070
  # Grant the app to send e-mails
1153
1071
  if comms:
1154
1072
  comms_role = comms.id.apply(
@@ -1167,29 +1085,20 @@ class DjangoDeployment(pulumi.ComponentResource):
1167
1085
  scope=comms.id,
1168
1086
  )
1169
1087
 
1170
- # Grant the app to purge the CDN endpoint
1171
- cdn_role = self._cdn_endpoint.id.apply(
1088
+ # Grant the app to manage the CDN endpoint (CDN Profile Contributor)
1089
+ cdn_profile_role = self._cdn_profile.id.apply(
1172
1090
  lambda scope: azure.authorization.get_role_definition(
1173
- role_definition_id="/426e0c7f-0c7e-4658-b36f-ff54d6c29b45",
1091
+ role_definition_id="ec156ff8-a8d1-4d15-830c-5b80698ca432",
1174
1092
  scope=scope,
1175
1093
  )
1176
1094
  )
1177
1095
 
1178
1096
  azure.authorization.RoleAssignment(
1179
- f"ra-{name}-cdn",
1097
+ f"ra-{name}-cdn-profile",
1180
1098
  principal_id=principal_id,
1181
1099
  principal_type=azure.authorization.PrincipalType.SERVICE_PRINCIPAL,
1182
- role_definition_id=cdn_role.id,
1183
- scope=self._cdn_endpoint.id,
1100
+ role_definition_id=cdn_profile_role.id,
1101
+ scope=self._cdn_profile.id,
1184
1102
  )
1185
1103
 
1186
1104
  return app
1187
-
1188
- def _strip_off_dns_zone_name(self, host: str, zone: azure.dns.Zone) -> pulumi.Output[str]:
1189
- """
1190
- Strip off the DNS zone name from the host name.
1191
-
1192
- :param host: The host name
1193
- :return: The host name without the DNS zone
1194
- """
1195
- return zone.name.apply(lambda name: host[: -len(name) - 1])