Skip to content

Commit 6b81787

Browse files
committed
feat(azure-tf): websites
1 parent a866a99 commit 6b81787

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2334
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
output "api_gateway_url" {
2+
value = azurerm_api_management.api.gateway_url
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
terraform {
2+
required_providers {
3+
azapi = {
4+
source = "Azure/azapi"
5+
}
6+
}
7+
}
8+
9+
locals {
10+
endpoint_name = "${var.stack_name}-cdn"
11+
default_origin_group_name = "website-origin-group"
12+
}
13+
14+
data "azurerm_client_config" "current" {}
15+
16+
# Create the CDN profile for the website
17+
resource "azurerm_cdn_profile" "website_profile" {
18+
name = "website-profile"
19+
resource_group_name = var.resource_group_name
20+
location = var.location
21+
sku = "Standard_Microsoft"
22+
}
23+
24+
25+
# Create the CDN endpoint with full configuration using azapi, due to limitations in the azurerm provider
26+
# This includes all origins, origin groups, and delivery rule, see https://github.com/hashicorp/terraform-provider-azurerm/issues/10771
27+
resource "azapi_resource" "cdn_endpoint" {
28+
type = "Microsoft.Cdn/profiles/endpoints@2024-09-01"
29+
name = local.endpoint_name
30+
parent_id = azurerm_cdn_profile.website_profile.id
31+
location = var.location
32+
33+
body = {
34+
properties = {
35+
isHttpAllowed = false
36+
isHttpsAllowed = true
37+
isCompressionEnabled = true
38+
contentTypesToCompress = [
39+
"text/html", "text/css", "application/javascript",
40+
"application/json", "image/svg+xml", "font/woff", "font/woff2"
41+
]
42+
43+
# Define all origins in one place
44+
origins = concat(
45+
# Storage origin (duplicated from Terraform resource)
46+
[{
47+
name = var.storage_account_name
48+
properties = {
49+
hostName = var.storage_account_primary_web_host
50+
originHostHeader = var.storage_account_primary_web_host
51+
enabled = true
52+
httpPort = 80
53+
httpsPort = 443
54+
}
55+
}],
56+
# API origins
57+
[for key in sort(keys(var.apis)) : {
58+
name = "api-origin-${key}"
59+
properties = {
60+
hostName = replace(replace(var.apis[key].gateway_url, "https://", ""), "/", "")
61+
originHostHeader = replace(replace(var.apis[key].gateway_url, "https://", ""), "/", "")
62+
httpPort = 80
63+
httpsPort = 443
64+
enabled = true
65+
}
66+
}]
67+
)
68+
69+
# Define all origin groups
70+
originGroups = concat(
71+
# Website origin group
72+
[{
73+
name = "website-origin-group"
74+
properties = {
75+
origins = [{
76+
id = "${azurerm_cdn_profile.website_profile.id}/endpoints/${local.endpoint_name}/origins/${var.storage_account_name}"
77+
}]
78+
}
79+
}],
80+
# API origin groups
81+
[for key in sort(keys(var.apis)) : {
82+
name = "api-origin-group-${key}"
83+
properties = {
84+
origins = [{
85+
id = "${azurerm_cdn_profile.website_profile.id}/endpoints/${local.endpoint_name}/origins/api-origin-${key}"
86+
}]
87+
}
88+
}]
89+
)
90+
91+
# Set default origin group
92+
defaultOriginGroup = {
93+
id = "${azurerm_cdn_profile.website_profile.id}/endpoints/${local.endpoint_name}/originGroups/website-origin-group"
94+
}
95+
96+
# Define delivery rules
97+
deliveryPolicy = {
98+
description = "API routing policy"
99+
rules = [
100+
for idx, key in { for i, k in sort(keys(var.apis)) : i => k } : {
101+
name = "forward_${key}"
102+
order = idx + 1
103+
conditions = [{
104+
name = "UrlPath"
105+
parameters = {
106+
typeName = "DeliveryRuleUrlPathMatchConditionParameters"
107+
operator = "BeginsWith"
108+
matchValues = ["/api/${key}"]
109+
transforms = ["Lowercase"]
110+
negateCondition = false
111+
}
112+
}]
113+
actions = [
114+
{
115+
name = "OriginGroupOverride"
116+
parameters = {
117+
typeName = "DeliveryRuleOriginGroupOverrideActionParameters"
118+
originGroup = {
119+
id = "${azurerm_cdn_profile.website_profile.id}/endpoints/${local.endpoint_name}/originGroups/api-origin-group-${key}"
120+
}
121+
}
122+
},
123+
{
124+
name = "UrlRewrite"
125+
parameters = {
126+
typeName = "DeliveryRuleUrlRewriteActionParameters"
127+
sourcePattern = "/api/${key}/"
128+
destination = "/"
129+
}
130+
}
131+
]
132+
}
133+
]
134+
}
135+
}
136+
}
137+
138+
response_export_values = ["properties.hostName"]
139+
140+
depends_on = [azurerm_cdn_profile.website_profile]
141+
}
142+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
output "cdn_url" {
2+
description = "The URL of the CDN endpoint"
3+
value = "https://${jsondecode(azapi_resource.cdn_endpoint.output).properties.hostName}"
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
variable "stack_name" {
2+
description = "The name of the stack"
3+
type = string
4+
}
5+
6+
variable "storage_account_id" {
7+
description = "The id of the storage account to use for the cdn"
8+
type = string
9+
}
10+
11+
variable "storage_account_name" {
12+
description = "The name of the storage account to use for the cdn"
13+
type = string
14+
}
15+
16+
variable "storage_account_primary_web_host" {
17+
description = "The primary web host of the storage account to use for the cdn"
18+
type = string
19+
}
20+
21+
variable "resource_group_name" {
22+
description = "The name of the resource group to use for the cdn"
23+
type = string
24+
}
25+
26+
variable "apis" {
27+
description = "Map of APIs and their gateway information"
28+
type = map(object({
29+
gateway_url = string
30+
# Add any other API properties you might need
31+
}))
32+
default = {}
33+
}
34+
35+
variable "location" {
36+
description = "The location/region where the resources will be created"
37+
type = string
38+
}

cloud/azure/deploytf/.nitric/modules/stack/main.tf

+11-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ resource "azurerm_storage_account" "storage" {
4040
}
4141
}
4242

43+
# Create a storage container for the website
44+
resource "azurerm_storage_account_static_website" "storage_website" {
45+
count = var.enable_website ? 1 : 0
46+
storage_account_id = azurerm_storage_account.storage[0].id
47+
index_document = var.website_root_index_document
48+
error_404_document = var.website_root_error_document
49+
50+
depends_on = [azurerm_storage_account.storage]
51+
}
52+
4353
# Create a keyvault if secrets are enabled
4454
resource "azurerm_key_vault" "keyvault" {
4555
count = var.enable_keyvault ? 1 : 0
@@ -250,5 +260,5 @@ resource "azurerm_container_app_environment" "environment" {
250260
"x-nitric-${local.stack_name}-type" = "stack"
251261
}
252262

253-
infrastructure_subnet_id = azurerm_subnet.database_infrastructure_subnet[0].id
263+
infrastructure_subnet_id = var.enable_database ? azurerm_subnet.database_infrastructure_subnet[0].id : null
254264
}

cloud/azure/deploytf/.nitric/modules/stack/outputs.tf

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ output "storage_account_queue_endpoint" {
5050
value = one(azurerm_storage_account.storage) != null ? one(azurerm_storage_account.storage).primary_queue_endpoint : null
5151
}
5252

53+
output "storage_account_web_host" {
54+
value = one(azurerm_storage_account.storage) != null ? one(azurerm_storage_account.storage).primary_web_host : null
55+
}
56+
5357
output "registry_username" {
5458
value = azurerm_container_registry.container_registry.admin_username
5559
}

cloud/azure/deploytf/.nitric/modules/stack/variables.tf

+18
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ variable "enable_database" {
2121
default = false
2222
}
2323

24+
variable "enable_website" {
25+
description = "Enable the creation of a website"
26+
type = bool
27+
default = false
28+
}
29+
30+
variable "website_root_index_document" {
31+
description = "The root index document for the website"
32+
type = string
33+
default = "index.html"
34+
}
35+
36+
variable "website_root_error_document" {
37+
description = "The root error document for the website"
38+
type = string
39+
default = "404.html"
40+
}
41+
2442
variable "location" {
2543
description = "The location/region where the resources will be created"
2644
type = string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module "template_files" {
2+
source = "hashicorp/dir/template"
3+
version = "1.0.2"
4+
5+
base_dir = var.local_directory
6+
}
7+
8+
locals {
9+
# Apply the base path logic for key transformation
10+
transformed_files = {
11+
for path, file in module.template_files.files : (
12+
var.base_path == "/" ?
13+
path :
14+
"${trimsuffix(var.base_path, "/")}/${path}"
15+
) => file
16+
}
17+
}
18+
19+
20+
resource "azurerm_storage_blob" "website_files" {
21+
for_each = local.transformed_files
22+
23+
name = trimprefix(each.key, "/")
24+
25+
storage_account_name = var.storage_account_name
26+
storage_container_name = "$web"
27+
type = "Block"
28+
source = each.value.source_path
29+
content_type = each.value.content_type
30+
31+
# required to detect file changes in Terraform
32+
content_md5 = each.value.digests.md5
33+
}
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# output "uploaded_files" {
2+
# value = {
3+
# for file, blob in azurerm_storage_blob.website_files :
4+
# file => blob.url
5+
# if blob.content_md5 != filemd5("${var.local_directory}/${file}") # Filter changed files
6+
# }
7+
# }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
variable "name" {
2+
description = "The name of the website"
3+
type = string
4+
}
5+
6+
variable "base_path" {
7+
description = "The base path for the website"
8+
type = string
9+
}
10+
11+
variable "local_directory" {
12+
description = "The local directory to deploy the website from"
13+
type = string
14+
}
15+
16+
variable "storage_account_name" {
17+
description = "The name of the storage account"
18+
type = string
19+
}
20+
21+

cloud/azure/deploytf/cdktf.json

+8
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@
5353
{
5454
"name": "sql",
5555
"source": "./.nitric/modules/sql"
56+
},
57+
{
58+
"name": "website",
59+
"source": "./.nitric/modules/website"
60+
},
61+
{
62+
"name": "cdn",
63+
"source": "./.nitric/modules/cdn"
5664
}
5765
],
5866
"context": {}

cloud/azure/deploytf/cdn.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2021 Nitric Technologies Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package deploytf
16+
17+
import (
18+
"slices"
19+
20+
"github.com/aws/jsii-runtime-go"
21+
"github.com/hashicorp/terraform-cdk-go/cdktf"
22+
"github.com/nitrictech/nitric/cloud/azure/deploytf/generated/cdn"
23+
"github.com/samber/lo"
24+
)
25+
26+
type ApiGateway struct {
27+
GatewayURL *string `json:"gateway_url"`
28+
}
29+
30+
// function to create a new cdn
31+
func (n *NitricAzureTerraformProvider) NewCdn(tfstack cdktf.TerraformStack) cdn.Cdn {
32+
apiGateways := make(map[string]ApiGateway)
33+
34+
sortedApiKeys := lo.Keys(n.Apis)
35+
slices.Sort(sortedApiKeys)
36+
37+
for _, apiName := range sortedApiKeys {
38+
api := n.Apis[apiName]
39+
apiGateways[apiName] = ApiGateway{
40+
GatewayURL: api.ApiGatewayUrlOutput(),
41+
}
42+
}
43+
44+
return cdn.NewCdn(tfstack, jsii.String("cdn"), &cdn.CdnConfig{
45+
StackName: n.Stack.StackNameOutput(),
46+
StorageAccountId: n.Stack.StorageAccountIdOutput(),
47+
StorageAccountName: n.Stack.StorageAccountNameOutput(),
48+
StorageAccountPrimaryWebHost: n.Stack.StorageAccountWebHostOutput(),
49+
ResourceGroupName: n.Stack.ResourceGroupNameOutput(),
50+
Location: jsii.String(n.Region),
51+
Apis: apiGateways,
52+
DependsOn: &[]cdktf.ITerraformDependable{n.Stack},
53+
})
54+
}

0 commit comments

Comments
 (0)