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.
- pulumi_django_azure/__init__.py +0 -2
- pulumi_django_azure/azure_helper.py +34 -40
- pulumi_django_azure/context_processors.py +42 -0
- pulumi_django_azure/django_deployment.py +153 -244
- pulumi_django_azure/management/commands/fix_cache_control.py +119 -0
- pulumi_django_azure/management/commands/purge_cache.py +10 -2
- pulumi_django_azure/management/commands/purge_cdn.py +10 -4
- pulumi_django_azure/management/commands/test_redis.py +248 -0
- pulumi_django_azure/middleware.py +26 -41
- pulumi_django_azure/settings.py +62 -34
- {pulumi_django_azure-1.0.28.dist-info → pulumi_django_azure-1.0.59.dist-info}/METADATA +251 -278
- pulumi_django_azure-1.0.59.dist-info/RECORD +14 -0
- {pulumi_django_azure-1.0.28.dist-info → pulumi_django_azure-1.0.59.dist-info}/WHEEL +1 -2
- pulumi_django_azure-1.0.28.dist-info/RECORD +0 -12
- pulumi_django_azure-1.0.28.dist-info/top_level.txt +0 -1
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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=
|
|
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(
|
|
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
|
-
|
|
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
|
-
#
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
861
|
-
|
|
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
|
|
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="
|
|
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
|
-
|
|
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
|
|
1171
|
-
|
|
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="
|
|
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=
|
|
1183
|
-
scope=self.
|
|
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])
|