pulumi-django-azure 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pulumi-django-azure might be problematic. Click here for more details.
- pulumi_django_azure/__init__.py +1 -0
- pulumi_django_azure/django_deployment.py +561 -0
- {pulumi_django_azure-1.0.2.dist-info → pulumi_django_azure-1.0.4.dist-info}/METADATA +1 -1
- pulumi_django_azure-1.0.4.dist-info/RECORD +7 -0
- pulumi_django_azure-1.0.4.dist-info/top_level.txt +1 -0
- pulumi_django_azure-1.0.2.dist-info/RECORD +0 -5
- pulumi_django_azure-1.0.2.dist-info/top_level.txt +0 -1
- {pulumi_django_azure-1.0.2.dist-info → pulumi_django_azure-1.0.4.dist-info}/LICENSE +0 -0
- {pulumi_django_azure-1.0.2.dist-info → pulumi_django_azure-1.0.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .django_deployment import DjangoDeployment # noqa: F401
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
from typing import Optional, Sequence
|
|
2
|
+
|
|
3
|
+
import pulumi
|
|
4
|
+
import pulumi_azure_native as azure
|
|
5
|
+
import pulumi_random
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DjangoDeployment(pulumi.ComponentResource):
|
|
9
|
+
HEALTH_CHECK_PATH = "/health-check"
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
name,
|
|
14
|
+
tenant_id: str,
|
|
15
|
+
resource_group_name: pulumi.Input[str],
|
|
16
|
+
vnet: azure.network.VirtualNetwork,
|
|
17
|
+
pgsql_ip_prefix: str,
|
|
18
|
+
appservice_ip_prefix: str,
|
|
19
|
+
app_service_sku: azure.web.SkuDescriptionArgs,
|
|
20
|
+
storage_account_name: str,
|
|
21
|
+
cdn_host: Optional[str],
|
|
22
|
+
opts=None,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Create a Django deployment.
|
|
26
|
+
|
|
27
|
+
:param name: The name of the deployment, will be used to name subresources.
|
|
28
|
+
:param tenant_id: The Entra tenant ID for the database authentication.
|
|
29
|
+
:param resource_group_name: The resource group name to create the resources in.
|
|
30
|
+
:param vnet: The virtual network to create the subnets in.
|
|
31
|
+
:param pgsql_ip_prefix: The IP prefix for the PostgreSQL subnet.
|
|
32
|
+
:param appservice_ip_prefix: The IP prefix for the app service subnet.
|
|
33
|
+
:param app_service_sku: The SKU for the app service plan.
|
|
34
|
+
:param storage_account_name: The name of the storage account. Should be unique across Azure.
|
|
35
|
+
:param cdn_host: A custom CDN host name (optional)
|
|
36
|
+
:param opts: The resource options
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
super().__init__("pkg:index:DjangoDeployment", name, None, opts)
|
|
40
|
+
|
|
41
|
+
# child_opts = pulumi.ResourceOptions(parent=self)
|
|
42
|
+
|
|
43
|
+
self._name = name
|
|
44
|
+
self._tenant_id = tenant_id
|
|
45
|
+
self._rg = resource_group_name
|
|
46
|
+
self._vnet = vnet
|
|
47
|
+
|
|
48
|
+
# Storage resources
|
|
49
|
+
self._create_storage(account_name=storage_account_name)
|
|
50
|
+
self._cdn_host = self._create_cdn(custom_host=cdn_host)
|
|
51
|
+
|
|
52
|
+
# PostgreSQL resources
|
|
53
|
+
self._create_database(ip_prefix=pgsql_ip_prefix)
|
|
54
|
+
|
|
55
|
+
# Subnet for the apps
|
|
56
|
+
self._app_subnet = self._create_subnet(
|
|
57
|
+
name="app-service",
|
|
58
|
+
prefix=appservice_ip_prefix,
|
|
59
|
+
delegation_service="Microsoft.Web/serverFarms",
|
|
60
|
+
service_endpoints=["Microsoft.Storage"],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Create App Service plan that will host all websites
|
|
64
|
+
self._app_service_plan = self._create_app_service_plan(sku=app_service_sku)
|
|
65
|
+
|
|
66
|
+
# Create a pgAdmin app
|
|
67
|
+
self._create_pgadmin_app()
|
|
68
|
+
|
|
69
|
+
def _create_storage(self, account_name: str):
|
|
70
|
+
# Create blob storage
|
|
71
|
+
self._storage_account = azure.storage.StorageAccount(
|
|
72
|
+
f"sa-{self._name}",
|
|
73
|
+
resource_group_name=self._rg,
|
|
74
|
+
account_name=account_name,
|
|
75
|
+
sku=azure.storage.SkuArgs(
|
|
76
|
+
name=azure.storage.SkuName.STANDARD_LRS,
|
|
77
|
+
),
|
|
78
|
+
kind=azure.storage.Kind.STORAGE_V2,
|
|
79
|
+
access_tier=azure.storage.AccessTier.HOT,
|
|
80
|
+
allow_blob_public_access=True,
|
|
81
|
+
public_network_access=azure.storage.PublicNetworkAccess.ENABLED,
|
|
82
|
+
enable_https_traffic_only=True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _create_cdn(self, custom_host: Optional[str]) -> pulumi.Output[str]:
|
|
86
|
+
"""
|
|
87
|
+
Create a CDN endpoint. If a host name is given, it will be used as the custom domain.
|
|
88
|
+
Otherwise, the default CDN host name will be returned.
|
|
89
|
+
|
|
90
|
+
:param custom_host: The custom domain (optional)
|
|
91
|
+
:return: The CDN host name
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
# Put CDN in front
|
|
95
|
+
cdn = azure.cdn.Profile(
|
|
96
|
+
f"cdn-{self._name}",
|
|
97
|
+
resource_group_name=self._rg,
|
|
98
|
+
location="global",
|
|
99
|
+
sku=azure.cdn.SkuArgs(
|
|
100
|
+
name=azure.cdn.SkuName.STANDARD_MICROSOFT,
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
endpoint_origin = self._storage_account.primary_endpoints.apply(
|
|
105
|
+
lambda primary_endpoints: primary_endpoints.blob.replace("https://", "").replace("/", "")
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self._cdn_endpoint = azure.cdn.Endpoint(
|
|
109
|
+
f"cdn-endpoint-{self._name}",
|
|
110
|
+
resource_group_name=self._rg,
|
|
111
|
+
location="global",
|
|
112
|
+
is_compression_enabled=True,
|
|
113
|
+
content_types_to_compress=[
|
|
114
|
+
"application/javascript",
|
|
115
|
+
"application/json",
|
|
116
|
+
"application/x-javascript",
|
|
117
|
+
"application/xml",
|
|
118
|
+
"text/css",
|
|
119
|
+
"text/html",
|
|
120
|
+
"text/javascript",
|
|
121
|
+
"text/plain",
|
|
122
|
+
],
|
|
123
|
+
is_http_allowed=False,
|
|
124
|
+
is_https_allowed=True,
|
|
125
|
+
profile_name=cdn.name,
|
|
126
|
+
origin_host_header=endpoint_origin,
|
|
127
|
+
origins=[azure.cdn.DeepCreatedOriginArgs(name="origin-storage", host_name=endpoint_origin)],
|
|
128
|
+
query_string_caching_behavior=azure.cdn.QueryStringCachingBehavior.IGNORE_QUERY_STRING,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
pulumi.export("cdn_cname", self._cdn_endpoint.host_name)
|
|
132
|
+
|
|
133
|
+
# Add custom domain if given
|
|
134
|
+
if custom_host:
|
|
135
|
+
azure.cdn.CustomDomain(
|
|
136
|
+
f"cdn-custom-domain-{self._name}",
|
|
137
|
+
resource_group_name=self._rg,
|
|
138
|
+
profile_name=cdn.name,
|
|
139
|
+
endpoint_name=self._cdn_endpoint.name,
|
|
140
|
+
host_name=custom_host,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return custom_host
|
|
144
|
+
else:
|
|
145
|
+
# Return the default CDN host name
|
|
146
|
+
return self._cdn_endpoint.host_name
|
|
147
|
+
|
|
148
|
+
def _create_database(self, ip_prefix: str):
|
|
149
|
+
# Create subnet for PostgreSQL
|
|
150
|
+
subnet = self._create_subnet(
|
|
151
|
+
name="pgsql",
|
|
152
|
+
prefix=ip_prefix,
|
|
153
|
+
delegation_service="Microsoft.DBforPostgreSQL/flexibleServers",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Create private DNS zone
|
|
157
|
+
dns = azure.network.PrivateZone(
|
|
158
|
+
f"dns-pgsql-{self._name}",
|
|
159
|
+
resource_group_name=self._rg,
|
|
160
|
+
location="global",
|
|
161
|
+
# The zone name must end with this to work with Azure DB
|
|
162
|
+
private_zone_name=f"{self._name}.postgres.database.azure.com",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Link the private DNS zone to the VNet in order to make resolving work
|
|
166
|
+
azure.network.VirtualNetworkLink(
|
|
167
|
+
f"vnet-link-pgsql-{self._name}",
|
|
168
|
+
resource_group_name=self._rg,
|
|
169
|
+
location="global",
|
|
170
|
+
private_zone_name=dns.name,
|
|
171
|
+
virtual_network=azure.network.SubResourceArgs(id=self._vnet.id),
|
|
172
|
+
registration_enabled=False,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Create PostgreSQL server
|
|
176
|
+
self._pgsql = azure.dbforpostgresql.Server(
|
|
177
|
+
f"pgsql-{self._name}",
|
|
178
|
+
resource_group_name=self._rg,
|
|
179
|
+
sku=azure.dbforpostgresql.SkuArgs(
|
|
180
|
+
name="Standard_B2s",
|
|
181
|
+
tier=azure.dbforpostgresql.SkuTier.BURSTABLE,
|
|
182
|
+
),
|
|
183
|
+
version="16",
|
|
184
|
+
auth_config=azure.dbforpostgresql.AuthConfigArgs(
|
|
185
|
+
password_auth=azure.dbforpostgresql.PasswordAuthEnum.DISABLED,
|
|
186
|
+
active_directory_auth=azure.dbforpostgresql.ActiveDirectoryAuthEnum.ENABLED,
|
|
187
|
+
tenant_id=self._tenant_id,
|
|
188
|
+
),
|
|
189
|
+
storage=azure.dbforpostgresql.StorageArgs(storage_size_gb=32),
|
|
190
|
+
network=azure.dbforpostgresql.NetworkArgs(
|
|
191
|
+
delegated_subnet_resource_id=subnet.id,
|
|
192
|
+
private_dns_zone_arm_resource_id=dns.id,
|
|
193
|
+
),
|
|
194
|
+
backup=azure.dbforpostgresql.BackupArgs(
|
|
195
|
+
backup_retention_days=7,
|
|
196
|
+
geo_redundant_backup=azure.dbforpostgresql.GeoRedundantBackupEnum.DISABLED,
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
pulumi.export("pgsql_host", self._pgsql.fully_qualified_domain_name)
|
|
201
|
+
|
|
202
|
+
def _create_subnet(
|
|
203
|
+
self, name, prefix, delegation_service: Optional[str] = None, service_endpoints: Sequence[str] = []
|
|
204
|
+
) -> azure.network.Subnet:
|
|
205
|
+
"""
|
|
206
|
+
Generic method to create a subnet with a delegation.
|
|
207
|
+
|
|
208
|
+
:param name: The name of the subnet
|
|
209
|
+
:param prefix: The IP prefix
|
|
210
|
+
:param delegation_service: The service to delegate to
|
|
211
|
+
:param service_endpoints: The service endpoints to enable
|
|
212
|
+
:return: The subnet
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
if delegation_service:
|
|
216
|
+
delegation_service = azure.network.DelegationArgs(
|
|
217
|
+
name=f"delegation-{name}-{self._name}",
|
|
218
|
+
service_name=delegation_service,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
service_endpoints = [azure.network.ServiceEndpointPropertiesFormatArgs(service=s) for s in service_endpoints]
|
|
222
|
+
|
|
223
|
+
return azure.network.Subnet(
|
|
224
|
+
f"subnet-{name}-{self._name}",
|
|
225
|
+
resource_group_name=self._rg,
|
|
226
|
+
virtual_network_name=self._vnet.name,
|
|
227
|
+
address_prefix=prefix,
|
|
228
|
+
delegations=[delegation_service],
|
|
229
|
+
service_endpoints=service_endpoints,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def _create_app_service_plan(self, sku: azure.web.SkuDescriptionArgs) -> azure.web.AppServicePlan:
|
|
233
|
+
# Create App Service plan
|
|
234
|
+
return azure.web.AppServicePlan(
|
|
235
|
+
f"asp-{self._name}",
|
|
236
|
+
resource_group_name=self._rg,
|
|
237
|
+
kind="Linux",
|
|
238
|
+
reserved=True,
|
|
239
|
+
sku=sku,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def _create_pgadmin_app(self):
|
|
243
|
+
# The app itself
|
|
244
|
+
app = azure.web.WebApp(
|
|
245
|
+
f"app-pgadmin-{self._name}",
|
|
246
|
+
resource_group_name=self._rg,
|
|
247
|
+
server_farm_id=self._app_service_plan.id,
|
|
248
|
+
virtual_network_subnet_id=self._app_subnet.id,
|
|
249
|
+
identity=azure.web.ManagedServiceIdentityArgs(
|
|
250
|
+
type=azure.web.ManagedServiceIdentityType.SYSTEM_ASSIGNED,
|
|
251
|
+
),
|
|
252
|
+
https_only=True,
|
|
253
|
+
site_config=azure.web.SiteConfigArgs(
|
|
254
|
+
ftps_state=azure.web.FtpsState.DISABLED,
|
|
255
|
+
linux_fx_version="DOCKER|dpage/pgadmin4",
|
|
256
|
+
health_check_path="/misc/ping",
|
|
257
|
+
app_settings=[
|
|
258
|
+
azure.web.NameValuePairArgs(name="DOCKER_REGISTRY_SERVER_URL", value="https://index.docker.io/v1"),
|
|
259
|
+
azure.web.NameValuePairArgs(name="DOCKER_ENABLE_CI", value="true"),
|
|
260
|
+
# azure.web.NameValuePairArgs(name="WEBSITE_HTTPLOGGING_RETENTION_DAYS", value="7"),
|
|
261
|
+
# pgAdmin settings
|
|
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
|
+
azure.web.NameValuePairArgs(name="PGADMIN_DEFAULT_EMAIL", value="dbadmin@dbadmin.net"),
|
|
266
|
+
azure.web.NameValuePairArgs(name="PGADMIN_DEFAULT_PASSWORD", value="dbadmin"),
|
|
267
|
+
],
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Create a storage container for persistent data (SMB share)
|
|
272
|
+
share = azure.storage.FileShare(
|
|
273
|
+
f"share-pgadmin-{self._name}",
|
|
274
|
+
resource_group_name=self._rg,
|
|
275
|
+
account_name=self._storage_account.name,
|
|
276
|
+
share_name="pgadmin",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Mount the storage container
|
|
280
|
+
azure.web.WebAppAzureStorageAccounts(
|
|
281
|
+
f"app-pgadmin-mount-{self._name}",
|
|
282
|
+
resource_group_name=self._rg,
|
|
283
|
+
name=app.name,
|
|
284
|
+
properties={
|
|
285
|
+
"pgadmin-data": azure.web.AzureStorageInfoValueArgs(
|
|
286
|
+
account_name=self._storage_account.name,
|
|
287
|
+
access_key=self._get_storage_account_access_keys(self._storage_account)[0].value,
|
|
288
|
+
mount_path="/var/lib/pgadmin",
|
|
289
|
+
share_name=share.name,
|
|
290
|
+
type=azure.web.AzureStorageType.AZURE_FILES,
|
|
291
|
+
)
|
|
292
|
+
},
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
pulumi.export("pgadmin_url", app.default_host_name.apply(lambda host: f"https://{host}"))
|
|
296
|
+
|
|
297
|
+
def _add_webapp_host(self, app: azure.web.WebApp, host: str, suffix: str):
|
|
298
|
+
"""
|
|
299
|
+
Because of a circular dependency, we need to create the certificate and the binding in two steps.
|
|
300
|
+
First we create a binding without a certificate,
|
|
301
|
+
then we create the certificate and then we update the binding.
|
|
302
|
+
|
|
303
|
+
The certificate needs the binding, and the binding needs the thumbprint of the certificate.
|
|
304
|
+
|
|
305
|
+
See also: https://github.com/pulumi/pulumi-azure-native/issues/578
|
|
306
|
+
|
|
307
|
+
:param app: The web app
|
|
308
|
+
:param host: The host name
|
|
309
|
+
:param suffix: A suffix to make the resource name unique
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
safe_host = host.replace(".", "-")
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
# Retrieve the existing binding - this will throw an exception if it doesn't exist
|
|
316
|
+
azure.web.get_web_app_host_name_binding(
|
|
317
|
+
resource_group_name=app.resource_group,
|
|
318
|
+
name=app.name,
|
|
319
|
+
host_name=host,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Create a managed certificate
|
|
323
|
+
# This will work because the binding exists actually
|
|
324
|
+
certificate = azure.web.Certificate(
|
|
325
|
+
f"cert-{suffix}-{safe_host}",
|
|
326
|
+
resource_group_name=self._rg,
|
|
327
|
+
server_farm_id=app.server_farm_id,
|
|
328
|
+
canonical_name=host,
|
|
329
|
+
host_names=[host],
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Create a new binding, replacing the old one,
|
|
333
|
+
# with the certificate
|
|
334
|
+
azure.web.WebAppHostNameBinding(
|
|
335
|
+
f"host-binding-{suffix}-{safe_host}",
|
|
336
|
+
resource_group_name=self._rg,
|
|
337
|
+
name=app.name,
|
|
338
|
+
host_name=host,
|
|
339
|
+
ssl_state=azure.web.SslState.SNI_ENABLED,
|
|
340
|
+
thumbprint=certificate.thumbprint,
|
|
341
|
+
)
|
|
342
|
+
except Exception:
|
|
343
|
+
# Create a binding without a certificate
|
|
344
|
+
azure.web.WebAppHostNameBinding(
|
|
345
|
+
f"host-binding-{suffix}-{safe_host}",
|
|
346
|
+
resource_group_name=self._rg,
|
|
347
|
+
name=app.name,
|
|
348
|
+
host_name=host,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def _get_storage_account_access_keys(
|
|
352
|
+
self, storage_account: azure.storage.StorageAccount
|
|
353
|
+
) -> Sequence[azure.storage.outputs.StorageAccountKeyResponse]:
|
|
354
|
+
"""
|
|
355
|
+
Helper function to get the access keys for a storage account.
|
|
356
|
+
|
|
357
|
+
:param storage_account: The storage account
|
|
358
|
+
:return: The access keys
|
|
359
|
+
"""
|
|
360
|
+
keys = pulumi.Output.all(self._rg, storage_account.name).apply(
|
|
361
|
+
lambda args: azure.storage.list_storage_account_keys(
|
|
362
|
+
resource_group_name=args[0],
|
|
363
|
+
account_name=args[1],
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return keys.keys
|
|
368
|
+
|
|
369
|
+
def add_database_administrator(self, object_id: str, user_name: str):
|
|
370
|
+
"""
|
|
371
|
+
Add an Entra ID as database administrator.
|
|
372
|
+
|
|
373
|
+
:param object_id: The object ID of the user
|
|
374
|
+
:param user_name: The user name (user@example.com)
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
azure.dbforpostgresql.Administrator(
|
|
378
|
+
# Must be random but a GUID
|
|
379
|
+
f"pgsql-admin-{user_name.replace('@', '_')}-{self._name}",
|
|
380
|
+
resource_group_name=self._rg,
|
|
381
|
+
server_name=self._pgsql.name,
|
|
382
|
+
object_id=object_id,
|
|
383
|
+
principal_name=user_name,
|
|
384
|
+
principal_type=azure.dbforpostgresql.PrincipalType.USER,
|
|
385
|
+
tenant_id=self._tenant_id,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
def add_django_website(
|
|
389
|
+
self,
|
|
390
|
+
name: str,
|
|
391
|
+
db_name: str,
|
|
392
|
+
repository_url: str,
|
|
393
|
+
repository_branch: str,
|
|
394
|
+
website_hosts: list[str],
|
|
395
|
+
django_settings_module: str,
|
|
396
|
+
environment_variables: dict[str, str] = {},
|
|
397
|
+
) -> azure.web.WebApp:
|
|
398
|
+
"""
|
|
399
|
+
Create a Django website with it's own database and storage containers.
|
|
400
|
+
|
|
401
|
+
:param name: The reference for the website, will be used to name subresources.
|
|
402
|
+
:param db_name: The name of the database to create.
|
|
403
|
+
:param repository_url: The URL of the Git repository.
|
|
404
|
+
:param repository_branch: The Git branch to deploy.
|
|
405
|
+
:param website_hosts: The list of custom host names for the website.
|
|
406
|
+
:param django_settings_module: The Django settings module to load.
|
|
407
|
+
:param environment_variables: A dictionary of environment variables to set.
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
# Create a database
|
|
411
|
+
db = azure.dbforpostgresql.Database(
|
|
412
|
+
f"db-{name}",
|
|
413
|
+
database_name=db_name,
|
|
414
|
+
resource_group_name=self._rg,
|
|
415
|
+
server_name=self._pgsql.name,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Container for media files
|
|
419
|
+
media_container = azure.storage.BlobContainer(
|
|
420
|
+
f"storage-container-{name}-media-{self._name}",
|
|
421
|
+
resource_group_name=self._rg,
|
|
422
|
+
account_name=self._storage_account.name,
|
|
423
|
+
public_access=azure.storage.PublicAccess.BLOB,
|
|
424
|
+
container_name=f"{name}-media",
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# Container for static files
|
|
428
|
+
static_container = azure.storage.BlobContainer(
|
|
429
|
+
f"storage-container-{name}-static-{self._name}",
|
|
430
|
+
resource_group_name=self._rg,
|
|
431
|
+
account_name=self._storage_account.name,
|
|
432
|
+
public_access=azure.storage.PublicAccess.BLOB,
|
|
433
|
+
container_name=f"{name}-static",
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# Create a Django Secret Key (random)
|
|
437
|
+
secret_key = pulumi_random.RandomString(f"django-secret-{name}-{self._name}", length=50)
|
|
438
|
+
|
|
439
|
+
# Convert environment variables to NameValuePairArgs
|
|
440
|
+
environment_variables = [
|
|
441
|
+
azure.web.NameValuePairArgs(
|
|
442
|
+
name=key,
|
|
443
|
+
value=value,
|
|
444
|
+
)
|
|
445
|
+
for key, value in environment_variables.items()
|
|
446
|
+
]
|
|
447
|
+
|
|
448
|
+
app = azure.web.WebApp(
|
|
449
|
+
f"app-{name}-{self._name}",
|
|
450
|
+
resource_group_name=self._rg,
|
|
451
|
+
server_farm_id=self._app_service_plan.id,
|
|
452
|
+
virtual_network_subnet_id=self._app_subnet.id,
|
|
453
|
+
identity=azure.web.ManagedServiceIdentityArgs(
|
|
454
|
+
type=azure.web.ManagedServiceIdentityType.SYSTEM_ASSIGNED,
|
|
455
|
+
),
|
|
456
|
+
https_only=True,
|
|
457
|
+
site_config=azure.web.SiteConfigArgs(
|
|
458
|
+
always_on=True,
|
|
459
|
+
health_check_path=self.HEALTH_CHECK_PATH,
|
|
460
|
+
ftps_state=azure.web.FtpsState.DISABLED,
|
|
461
|
+
python_version="3.12",
|
|
462
|
+
# scm_type=azure.web.ScmType.EXTERNAL_GIT,
|
|
463
|
+
linux_fx_version="PYTHON|3.12",
|
|
464
|
+
app_settings=[
|
|
465
|
+
# Build settings
|
|
466
|
+
azure.web.NameValuePairArgs(name="SCM_DO_BUILD_DURING_DEPLOYMENT", value="true"),
|
|
467
|
+
azure.web.NameValuePairArgs(name="PRE_BUILD_COMMAND", value="cicd/pre_build.sh"),
|
|
468
|
+
azure.web.NameValuePairArgs(name="POST_BUILD_COMMAND", value="cicd/post_build.sh"),
|
|
469
|
+
azure.web.NameValuePairArgs(name="DISABLE_COLLECTSTATIC", value="true"),
|
|
470
|
+
azure.web.NameValuePairArgs(name="HEALTH_CHECK_PATH", value=self.HEALTH_CHECK_PATH),
|
|
471
|
+
# Django settings
|
|
472
|
+
# azure.web.NameValuePairArgs(name="DEBUG", value="true"),
|
|
473
|
+
azure.web.NameValuePairArgs(name="DJANGO_SETTINGS_MODULE", value=django_settings_module),
|
|
474
|
+
azure.web.NameValuePairArgs(name="DJANGO_SECRET_KEY", value=secret_key.result),
|
|
475
|
+
azure.web.NameValuePairArgs(name="DJANGO_ALLOWED_HOSTS", value=",".join(website_hosts)),
|
|
476
|
+
# Storage settings
|
|
477
|
+
azure.web.NameValuePairArgs(name="AZURE_STORAGE_ACCOUNT_NAME", value=self._storage_account.name),
|
|
478
|
+
azure.web.NameValuePairArgs(name="AZURE_STORAGE_CONTAINER_STATICFILES", value=static_container.name),
|
|
479
|
+
azure.web.NameValuePairArgs(name="AZURE_STORAGE_CONTAINER_MEDIA", value=media_container.name),
|
|
480
|
+
# CDN
|
|
481
|
+
azure.web.NameValuePairArgs(name="CDN_HOST", value=self._cdn_host),
|
|
482
|
+
# Database settings
|
|
483
|
+
azure.web.NameValuePairArgs(name="DB_HOST", value=self._pgsql.fully_qualified_domain_name),
|
|
484
|
+
azure.web.NameValuePairArgs(name="DB_NAME", value=db.name),
|
|
485
|
+
azure.web.NameValuePairArgs(name="DB_USER", value=f"{name}_managed_identity"),
|
|
486
|
+
*environment_variables,
|
|
487
|
+
],
|
|
488
|
+
),
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# We need this to create the database role and grant permissions
|
|
492
|
+
principal_id = app.identity.apply(lambda identity: identity.principal_id)
|
|
493
|
+
pulumi.export(f"{name}_site_principal_id", principal_id)
|
|
494
|
+
pulumi.export(f"{name}_site_db_user", f"{name}_managed_identity")
|
|
495
|
+
|
|
496
|
+
# We need this to verify custom domains
|
|
497
|
+
pulumi.export(f"{name}_site_domain_verification_id", app.custom_domain_verification_id)
|
|
498
|
+
pulumi.export(f"{name}_site_domain_cname", app.default_host_name)
|
|
499
|
+
|
|
500
|
+
for host in website_hosts:
|
|
501
|
+
self._add_webapp_host(app=app, host=host, suffix=f"{name}-{self._name}")
|
|
502
|
+
|
|
503
|
+
# To enable deployment from GitLab
|
|
504
|
+
azure.web.WebAppSourceControl(
|
|
505
|
+
f"app-{name}-sourcecontrol-{self._name}",
|
|
506
|
+
resource_group_name=self._rg,
|
|
507
|
+
name=app.name,
|
|
508
|
+
repo_url=repository_url,
|
|
509
|
+
branch=repository_branch,
|
|
510
|
+
is_git_hub_action=False,
|
|
511
|
+
is_manual_integration=True,
|
|
512
|
+
is_mercurial=False,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Where we can retrieve the SSH key
|
|
516
|
+
pulumi.export(
|
|
517
|
+
f"{name}_deploy_ssh_key_url", app.name.apply(lambda name: f"https://{name}.scm.azurewebsites.net/api/sshkey?ensurePublicKey=1")
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
# Find the role for Storage Blob Data Contributor
|
|
521
|
+
storage_role = self._storage_account.id.apply(
|
|
522
|
+
lambda scope: azure.authorization.get_role_definition(
|
|
523
|
+
role_definition_id="ba92f5b4-2d11-453d-a403-e96b0029c9fe",
|
|
524
|
+
scope=scope,
|
|
525
|
+
)
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Grant the app access to the storage account
|
|
529
|
+
azure.authorization.RoleAssignment(
|
|
530
|
+
f"ra-{name}-storage",
|
|
531
|
+
principal_id=principal_id,
|
|
532
|
+
principal_type=azure.authorization.PrincipalType.SERVICE_PRINCIPAL,
|
|
533
|
+
role_definition_id=storage_role.id,
|
|
534
|
+
scope=self._storage_account.id,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
# Create a CORS rules for this website
|
|
538
|
+
if website_hosts:
|
|
539
|
+
origins = [f"https://{host}" for host in website_hosts]
|
|
540
|
+
else:
|
|
541
|
+
origins = ["*"]
|
|
542
|
+
|
|
543
|
+
azure.storage.BlobServiceProperties(
|
|
544
|
+
f"sa-{name}-blob-properties",
|
|
545
|
+
resource_group_name=self._rg,
|
|
546
|
+
account_name=self._storage_account.name,
|
|
547
|
+
blob_services_name="default",
|
|
548
|
+
cors=azure.storage.CorsRulesArgs(
|
|
549
|
+
cors_rules=[
|
|
550
|
+
azure.storage.CorsRuleArgs(
|
|
551
|
+
allowed_headers=["*"],
|
|
552
|
+
allowed_methods=["GET", "OPTIONS", "HEAD"],
|
|
553
|
+
allowed_origins=origins,
|
|
554
|
+
exposed_headers=["Access-Control-Allow-Origin"],
|
|
555
|
+
max_age_in_seconds=86400,
|
|
556
|
+
)
|
|
557
|
+
]
|
|
558
|
+
),
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
return app
|
|
@@ -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=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,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pulumi_django_azure
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
pulumi_django_azure-1.0.2.dist-info/LICENSE,sha256=NX2LN3U319Zaac8b7ZgfNOco_nTBbN531X_M_13niSg,1087
|
|
2
|
-
pulumi_django_azure-1.0.2.dist-info/METADATA,sha256=PSuScGCzqH0jEnkJmnhctwI-63jiFlVq5Ni5DCmURLA,7887
|
|
3
|
-
pulumi_django_azure-1.0.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
4
|
-
pulumi_django_azure-1.0.2.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
5
|
-
pulumi_django_azure-1.0.2.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
File without changes
|
|
File without changes
|