Skip to content

Commit 3a77263

Browse files
nickdalaibersanoMSakhan-msft
authored
App config (#23)
* feat: initial app config tf module * feat: add keys and features to app config module * fix: made key and feat props optional and minor updates * chore: comment out deprecated_retention_policy * fix: rename vnet variable for acr to hub * feat: add app config hub deployment * feat: add keys for app config to locals * feat: add user assigned identities to app service and add roles to azconfig * feat: update role on uid * feat: remove unnecessary roles from azconfig * feat: move app config to spoke and add 2nd config for prod * feat: setup app config key names, remove app settings and add app config connection string * Changes for Azure App Configuration * fix: add app config uri to app deployment * fix: deprecated args in azurerm v4 * change prefix for app config * Change from connection string to endpoint for the App Config * Database configuration set by service connector * System assigned managed identity --------- Co-authored-by: Isabelle Bersano <[email protected]> Co-authored-by: Isabelle Bersano <[email protected]> Co-authored-by: Adnan Khan <[email protected]>
1 parent be4795e commit 3a77263

File tree

21 files changed

+484
-214
lines changed

21 files changed

+484
-214
lines changed

apps/contoso-fiber/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@
149149
<dependency>
150150
<groupId>org.apache.commons</groupId>
151151
<artifactId>commons-lang3</artifactId>
152+
</dependency>
153+
154+
<dependency>
155+
<groupId>com.azure.spring</groupId>
156+
<artifactId>spring-cloud-azure-appconfiguration-config-web</artifactId>
152157
</dependency>
153158

154159
</dependencies>

apps/contoso-fiber/src/main/java/com/contoso/cams/serviceplan/ServicePlanExceptionSimulator.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
package com.contoso.cams.serviceplan;
22

33
import org.apache.commons.lang3.StringUtils;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.stereotype.Component;
46

57
/*
68
This utility class is used to simulate exceptions to test the CircuitBreaker and Retry implmentations
79
based on the error rate set in the environment variable CONTOSO_RETRY_DEMO
810
*/
11+
12+
@Component
913
public class ServicePlanExceptionSimulator {
1014

1115
private static int requestCount = 0;
1216

17+
@Value("${contoso.retry.demo}")
18+
private String demo;
19+
1320
/**
1421
* Check if exception should be thrown based on error rate
1522
*/
16-
public static void checkAndthrowExceptionIfEnabled() {
23+
public void checkAndthrowExceptionIfEnabled() {
1724
int errorRate = getErrorRate();
1825

1926
// if error rate = 0 then return
@@ -31,11 +38,11 @@ public static void checkAndthrowExceptionIfEnabled() {
3138
* 1 = fail request every time
3239
* 2 = fail request every 2nd time
3340
*/
34-
private static int getErrorRate() {
41+
private int getErrorRate() {
3542
int errorRate = 0;
36-
String retryDemo = System.getenv("CONTOSO_RETRY_DEMO");
37-
if (!StringUtils.isEmpty(retryDemo)) {
38-
errorRate = Integer.parseInt(retryDemo);
43+
44+
if (!StringUtils.isEmpty(demo)) {
45+
errorRate = Integer.parseInt(demo);
3946
}
4047
return errorRate;
4148
}

apps/contoso-fiber/src/main/java/com/contoso/cams/serviceplan/ServicePlanService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.Optional;
55
import java.util.stream.Collectors;
66

7+
import org.springframework.beans.factory.annotation.Autowired;
78
import org.springframework.stereotype.Service;
89

910
import com.contoso.cams.model.ServicePlan;
@@ -17,6 +18,9 @@ public class ServicePlanService {
1718

1819
private final ServicePlanRepository planRepository;
1920

21+
@Autowired
22+
private final ServicePlanExceptionSimulator servicePlanExceptionSimulator;
23+
2024
public List<ServicePlanDto> getServicePlans() {
2125
List<ServicePlan> servicePlans = planRepository.findAll();
2226
List<ServicePlanDto> servicePlanDtos = servicePlans.stream()
@@ -29,7 +33,7 @@ public List<ServicePlanDto> getServicePlans() {
2933
servicePlan.getIsDefault()))
3034
.collect(Collectors.toList());
3135

32-
ServicePlanExceptionSimulator.checkAndthrowExceptionIfEnabled();
36+
servicePlanExceptionSimulator.checkAndthrowExceptionIfEnabled();
3337

3438
return servicePlanDtos;
3539
}

apps/contoso-fiber/src/main/resources/application.properties

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
## application name
2+
spring.application.name=contoso-fiber
3+
14
# Contoso Configuration
2-
contoso.retry.demo=0
5+
contoso.retry.demo=${CONTOSO_RETRY_DEMO}
36

47
## email, queue, demo-load, demo-dead-letter
58
contoso.suport-guide.request.service=${CONTOSO_SUPPORT_GUIDE_REQUEST_SERVICE}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
spring.cloud.azure.appconfiguration.stores[0].endpoint=${APP_CONFIGURATION_ENDPOINT}
2+
spring.cloud.azure.appconfiguration.stores[0].selects[0].key-filter=/contoso-fiber/

infra/shared/terraform/modules/acr/main.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ resource "azurerm_private_dns_zone_virtual_network_link" "virtual_network_link_a
9696
count = var.environment == "prod" ? 1 : 0
9797
name = "privatelink.azurecr.io"
9898
private_dns_zone_name = azurerm_private_dns_zone.dns_for_acr[0].name
99-
virtual_network_id = var.spoke_vnet_id
99+
virtual_network_id = var.hub_vnet_id
100100
resource_group_name = var.resource_group
101101
}
102102

@@ -118,4 +118,4 @@ resource "azurerm_private_endpoint" "acr_pe" {
118118
is_manual_connection = false
119119
subresource_names = ["registry"]
120120
}
121-
}
121+
}

infra/shared/terraform/modules/acr/variables.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ variable "private_endpoint_subnet_id" {
4949
default = null
5050
}
5151

52-
variable "spoke_vnet_id" {
52+
variable "hub_vnet_id" {
5353
type = string
5454
description = "The ID of the Spoke VNET"
5555
default = null
56-
}
56+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
terraform {
2+
required_providers {
3+
azurecaf = {
4+
source = "aztfmod/azurecaf"
5+
version = "1.2.26"
6+
}
7+
}
8+
}
9+
10+
data "azuread_client_config" "current" {}
11+
12+
# App Configuration naming convention using azurecaf_name module.
13+
resource "azurecaf_name" "azurerm_app_config" {
14+
name = var.application_name
15+
resource_type = "azurerm_app_configuration"
16+
suffixes = [var.environment]
17+
}
18+
19+
# Create Azure App Configuration
20+
21+
resource "azurerm_app_configuration" "app_config" {
22+
name = azurecaf_name.azurerm_app_config.result
23+
resource_group_name = var.resource_group
24+
location = var.location
25+
26+
sku = var.environment == "prod" ? "standard" : "free"
27+
28+
purge_protection_enabled = var.environment == "prod" ? true : false
29+
30+
public_network_access = var.environment == "prod" ? "Disabled" : "Enabled"
31+
32+
local_auth_enabled = var.environment == "prod" ? false : true
33+
34+
dynamic "replica" {
35+
for_each = var.replica_location != null ? [var.replica_location] : []
36+
content {
37+
location = replica.value
38+
name = "AppConfig${var.environment}${replica.value}"
39+
}
40+
}
41+
}
42+
43+
# Create App Configuration Features
44+
45+
resource "azurerm_app_configuration_feature" "feature" {
46+
for_each = var.features != null ? { for idx, feature in var.features : idx => feature } : {}
47+
configuration_store_id = azurerm_app_configuration.app_config.id
48+
description = each.value.description
49+
name = each.value.name
50+
51+
enabled = each.value.enabled
52+
locked = each.value.locked
53+
label = each.value.label
54+
}
55+
56+
# Create App Configuration Keys
57+
58+
resource "azurerm_app_configuration_key" "key" {
59+
for_each = var.keys != null ? { for idx, key in var.keys : idx => key } : {}
60+
configuration_store_id = azurerm_app_configuration.app_config.id
61+
key = each.value.key
62+
type = each.value.type
63+
content_type = each.value.type == "kv" ? each.value.content_type : null
64+
value = each.value.type == "kv" ? each.value.value : null
65+
vault_key_reference = each.value.type == "vault" ? each.value.vault_key_reference : null
66+
label = each.value.label
67+
locked = each.value.locked
68+
}
69+
70+
# Create role assignments
71+
72+
resource "azurerm_role_assignment" "app_service_reader_user_role_assignment" {
73+
principal_id = var.app_service_identity_principal_id
74+
role_definition_name = "App Configuration Data Reader"
75+
scope = azurerm_app_configuration.app_config.id
76+
}
77+
78+
# For demo purposes, allow current user access to the app config
79+
# Note: when running as a service principal, this is also needed
80+
81+
resource "azurerm_role_assignment" "azconfig_owner_user_role_assignment" {
82+
scope = azurerm_app_configuration.app_config.id
83+
role_definition_name = "App Configuration Data Owner"
84+
principal_id = data.azuread_client_config.current.object_id
85+
}
86+
87+
# Create Private DNS Zone and Endpoint for App Configuration
88+
89+
resource "azurerm_private_dns_zone" "dns_for_azconfig" {
90+
count = var.environment == "prod" ? 1 : 0
91+
name = "privatelink.azconfig.io"
92+
resource_group_name = var.resource_group
93+
}
94+
95+
resource "azurerm_private_dns_zone_virtual_network_link" "virtual_network_link_azconfig" {
96+
count = var.environment == "prod" ? 1 : 0
97+
name = "privatelink.azconfig.io"
98+
private_dns_zone_name = azurerm_private_dns_zone.dns_for_azconfig[0].name
99+
virtual_network_id = var.spoke_vnet_id
100+
resource_group_name = var.resource_group
101+
}
102+
103+
resource "azurerm_private_endpoint" "azconfig_pe" {
104+
count = var.environment == "prod" ? 1 : 0
105+
name = "private-endpoint-ac"
106+
location = var.location
107+
resource_group_name = var.resource_group
108+
subnet_id = var.private_endpoint_subnet_id
109+
110+
private_dns_zone_group {
111+
name = "privatednsazconfigzonegroup"
112+
private_dns_zone_ids = [azurerm_private_dns_zone.dns_for_azconfig[0].id]
113+
}
114+
115+
private_service_connection {
116+
name = "peconnection-azconfig"
117+
private_connection_resource_id = azurerm_app_configuration.app_config.id
118+
is_manual_connection = false
119+
subresource_names = ["configurationStores"]
120+
}
121+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
output "azconfig_name" {
2+
value = azurerm_app_configuration.app_config.name
3+
description = "The Azure App Configuration Name."
4+
}
5+
6+
output "azconfig_uri" {
7+
value = azurerm_app_configuration.app_config.endpoint
8+
description = "The Azure App Configuration URI."
9+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
variable "resource_group" {
2+
type = string
3+
description = "The resource group"
4+
}
5+
6+
variable "environment" {
7+
type = string
8+
description = "The environment (dev, test, prod...)"
9+
default = "dev"
10+
}
11+
12+
variable "location" {
13+
type = string
14+
description = "The Azure region where all resources in this example should be created"
15+
}
16+
17+
variable "application_name" {
18+
type = string
19+
description = "The name of your application"
20+
}
21+
22+
variable "aca_identity_principal_id" {
23+
type = string
24+
description = "The principal id of the identity of the container app"
25+
}
26+
27+
variable "features" {
28+
type = list(object({
29+
description = optional(string)
30+
name = string
31+
enabled = optional(bool, false)
32+
locked = optional(bool, false)
33+
label = optional(string)
34+
}))
35+
default = null
36+
37+
description = "The features to create in the App Configuration"
38+
}
39+
40+
variable "keys" {
41+
type = list(object({
42+
key = string
43+
content_type = optional(string)
44+
label = optional(string)
45+
value = optional(string)
46+
locked = optional(bool)
47+
type = optional(string, "kv")
48+
vault_key_reference = optional(string)
49+
}))
50+
default = []
51+
52+
validation {
53+
condition = alltrue([
54+
for k in var.keys :
55+
(k.type == "kv" && k.value != null) || (k.type == "vault" && k.vault_key_reference != null)
56+
])
57+
error_message = "Type must be kv or vault. If vault, vault_key_reference must be set. If kv, value must be set."
58+
}
59+
60+
description = "The keys to create in the App Configuration"
61+
}
62+
63+
variable "replica_location" {
64+
type = string
65+
description = "The location of the replica"
66+
default = null
67+
}
68+
69+
variable "private_endpoint_subnet_id" {
70+
type = string
71+
description = "The ID of the subnet where the private endpoint should be created"
72+
default = null
73+
}
74+
75+
variable "spoke_vnet_id" {
76+
type = string
77+
description = "The ID of the Spoke VNET"
78+
default = null
79+
}
80+
81+
variable "app_service_identity_principal_id" {
82+
type = string
83+
description = "The User Assigned identity id of the App Service"
84+
}

0 commit comments

Comments
 (0)