Skip to content

Commit 747b2e5

Browse files
authored
feat(contrib/apim-callout): Deploy to Azure button + production hardening (#4641)
### What does this PR do? Adds one-click "Deploy to Azure" infrastructure-as-code for the APIM callout service introduced in #4620. ### Key components - **`deploy/azure/main.bicep`** — Top-level Bicep template orchestrating all modules - **`deploy/azure/modules/`** — Modular Bicep files: VNet + subnets (`networking`), Azure Container Apps (`container-app`), Datadog Agent ACI sidecar (`agent`), private DNS (`dns`), APIM VNet integration (`apim-network`), and optional policy injection (`apim-policy`) - **`deploy/azure/azuredeploy.json`** — Compiled ARM template for the "Deploy to Azure" button - **`deploy/azure/deploy.sh`** — CLI deployment script with validation, what-if preview, and rollback - **`deploy/azure/test-callout.sh`** — Post-deployment smoke test - **`deploy/azure/policies/`** — APIM XML policy templates (inbound, outbound, full) - **`deploy/boomi/`** — Boomi gateway policy templates (Groovy scripts + JSON callout configs) ### Motivation Provide a turnkey deployment experience so customers can go from zero to a running APIM callout setup with a single click in the Azure Portal, or via `az deployment` / `deploy.sh` for CLI users. ### Describe how you validated your changes - `az bicep build` compiles without errors - Full E2E deployment to Azure: VNet, ACA, ACI (DD Agent), private DNS, APIM integration all provisioned and functional - `test-callout.sh` passes against the deployed infrastructure - "Deploy to Azure" button tested in Azure Portal ### Additional Notes - Depends on #4620 (base branch) for the Go service and Dockerfile - The ARM template (`azuredeploy.json`) is compiled from Bicep — marked as `linguist-generated` in `.gitattributes` Co-authored-by: eliott.bouhana <[email protected]>
1 parent 3ded665 commit 747b2e5

20 files changed

Lines changed: 3825 additions & 0 deletions

contrib/azure/apim-callout/deploy/azure/azuredeploy.json

Lines changed: 1490 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contrib/azure/apim-callout/deploy/azure/deploy.sh

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Datadog APIM Callout — Bicep Orchestrator
2+
// Deploys: VNet + ACI (DD Agent) + ACA (callout service) + optional APIM policy
3+
//
4+
// Usage:
5+
// az deployment group create -g <rg> -f main.bicep -p @parameters/default.bicepparam \
6+
// datadogApiKey=<key> apimServiceName=<name> targetApiIds='["api1"]' \
7+
// logAnalyticsWorkspaceId=<id>
8+
9+
metadata description = 'Datadog APIM Callout — deploy VNet + ACI Agent + ACA callout + APIM policy. Do not edit azuredeploy.json manually; regenerate with: az bicep build -f main.bicep --outfile azuredeploy.json'
10+
11+
targetScope = 'resourceGroup'
12+
13+
// --- Required parameters ---
14+
15+
@description('Datadog API key (never logged or output)')
16+
@secure()
17+
param datadogApiKey string
18+
19+
@description('Name of the existing APIM service')
20+
param apimServiceName string
21+
22+
@description('Resource group of the APIM service (defaults to this RG)')
23+
param apimResourceGroup string = resourceGroup().name
24+
25+
@description('Array of APIM API IDs to apply the policy to (empty = All APIs)')
26+
param targetApiIds array = []
27+
28+
@description('Resource ID of an existing Log Analytics workspace (optional, enables ACA log collection and diagnostic settings)')
29+
param logAnalyticsWorkspaceId string = ''
30+
31+
// --- Optional parameters ---
32+
33+
@description('Resource name prefix for all resources')
34+
param namePrefix string = 'dd-apim'
35+
36+
@description('Azure region (defaults to resource group location)')
37+
param location string = resourceGroup().location
38+
39+
@description('Datadog site')
40+
@allowed([
41+
'datadoghq.com'
42+
'us3.datadoghq.com'
43+
'us5.datadoghq.com'
44+
'datadoghq.eu'
45+
'ap1.datadoghq.com'
46+
'ap2.datadoghq.com'
47+
'ddog-gov.com'
48+
'datad0g.com' // Datadog staging (internal use only)
49+
])
50+
param datadogSite string = 'datadoghq.com'
51+
52+
@description('Whether to deploy the Datadog policy to APIM APIs')
53+
param deployPolicy bool = false
54+
55+
@description('Enforce HTTPS on the callout ingress (ACA terminates TLS automatically)')
56+
param enableHttps bool = false
57+
58+
@description('Container image for the callout service')
59+
param containerImage string = 'ghcr.io/datadog/dd-trace-go/apim-callout:latest'
60+
61+
@description('VNet address space')
62+
param vnetAddressPrefix string = '10.0.0.0/16'
63+
64+
@description('Resource ID of an existing VNet (empty = create new)')
65+
param existingVnetId string = ''
66+
67+
@description('Resource ID of an existing ACA subnet (empty = create new)')
68+
param existingAcaSubnetId string = ''
69+
70+
@description('Resource ID of an existing ACI subnet (empty = create new)')
71+
param existingAciSubnetId string = ''
72+
73+
@description('Enable CanNotDelete locks on critical resources (disable for dev/test)')
74+
param enableLocks bool = true
75+
76+
@description('Minimum replicas for the callout container app')
77+
@minValue(0)
78+
@maxValue(30)
79+
param minReplicas int = 1
80+
81+
@description('Maximum replicas for the callout container app')
82+
@minValue(1)
83+
@maxValue(300)
84+
param maxReplicas int = 10
85+
86+
@description('KEDA HTTP scaler concurrent requests threshold')
87+
param concurrentRequestsThreshold string = '20'
88+
89+
@description('Custom tags to apply to all resources (merged with default dd-component tag)')
90+
param customTags object = {}
91+
92+
// --- Policy XML ---
93+
// loadTextContent resolves relative to the file it appears in.
94+
var policyXml = loadTextContent('policies/azure-apim-full.xml')
95+
96+
// --- Module: Networking (always) ---
97+
module networking './modules/networking.bicep' = {
98+
name: '${namePrefix}-networking'
99+
params: {
100+
location: location
101+
namePrefix: namePrefix
102+
vnetAddressPrefix: vnetAddressPrefix
103+
existingVnetId: existingVnetId
104+
existingAcaSubnetId: existingAcaSubnetId
105+
existingAciSubnetId: existingAciSubnetId
106+
customTags: customTags
107+
}
108+
}
109+
110+
// --- Module: DD Agent on ACI (always) ---
111+
module agent './modules/agent.bicep' = {
112+
name: '${namePrefix}-agent'
113+
params: {
114+
location: location
115+
namePrefix: namePrefix
116+
datadogApiKey: datadogApiKey
117+
datadogSite: datadogSite
118+
aciSubnetId: networking.outputs.aciSubnetId
119+
enableLocks: enableLocks
120+
customTags: customTags
121+
}
122+
}
123+
124+
// --- Module: Container App (always) ---
125+
module containerApp './modules/container-app.bicep' = {
126+
name: '${namePrefix}-container-app'
127+
params: {
128+
location: location
129+
namePrefix: namePrefix
130+
containerImage: containerImage
131+
agentHost: agent.outputs.agentIp
132+
minReplicas: minReplicas
133+
maxReplicas: maxReplicas
134+
concurrentRequestsThreshold: concurrentRequestsThreshold
135+
acaSubnetId: networking.outputs.acaSubnetId
136+
logAnalyticsWorkspaceId: logAnalyticsWorkspaceId
137+
enableHttps: enableHttps
138+
enableLocks: enableLocks
139+
customTags: customTags
140+
}
141+
}
142+
143+
// --- Module: Private DNS (resolves internal ACA FQDN within the VNet) ---
144+
module dns './modules/dns.bicep' = {
145+
name: '${namePrefix}-dns'
146+
params: {
147+
namePrefix: namePrefix
148+
acaDomain: containerApp.outputs.acaDefaultDomain
149+
acaStaticIp: containerApp.outputs.acaStaticIp
150+
vnetId: networking.outputs.vnetId
151+
customTags: customTags
152+
}
153+
}
154+
155+
// --- Module: APIM Network (configures VNet integration on existing APIM) ---
156+
// Scoped to the APIM resource group to support cross-RG deployments.
157+
module apimNetwork './modules/apim-network.bicep' = {
158+
name: '${namePrefix}-apim-network'
159+
scope: resourceGroup(apimResourceGroup)
160+
params: {
161+
apimServiceName: apimServiceName
162+
apimResourceGroup: apimResourceGroup
163+
location: location
164+
apimSubnetId: networking.outputs.apimSubnetId
165+
customTags: customTags
166+
}
167+
}
168+
169+
// --- Module: APIM Policy (always invoked for outputs; resource conditional inside) ---
170+
// Scoped to the APIM resource group to support cross-RG deployments.
171+
// Depends on apimNetwork to ensure VNet is configured before policy injection.
172+
module apimPolicy './modules/apim-policy.bicep' = {
173+
name: '${namePrefix}-apim-policy'
174+
scope: resourceGroup(apimResourceGroup)
175+
params: {
176+
deployPolicy: deployPolicy
177+
apimServiceName: apimServiceName
178+
targetApiIds: targetApiIds
179+
calloutBaseUrl: containerApp.outputs.calloutBaseUrl
180+
policyXml: policyXml
181+
}
182+
}
183+
184+
// --- Outputs ---
185+
output acaFqdn string = containerApp.outputs.acaFqdn
186+
output calloutBaseUrl string = containerApp.outputs.calloutBaseUrl
187+
output agentIp string = agent.outputs.agentIp
188+
output vnetId string = networking.outputs.vnetId
189+
output apimSubnetId string = networking.outputs.apimSubnetId
190+
output policyPatchCommand string = apimPolicy.outputs.policyPatchCommand
191+
output policyFragments object = apimPolicy.outputs.policyFragments
192+
output appliedApiIds array = apimPolicy.outputs.appliedApiIds
193+
output deploymentSummary string = 'Callout: ${containerApp.outputs.calloutBaseUrl} | Agent: ${agent.outputs.agentIp} | Policy deployed: ${string(deployPolicy)}'
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// ACI Datadog Agent module
2+
// Always deployed — DD Agent is mandatory for Remote Configuration.
3+
4+
@description('Azure region')
5+
param location string
6+
7+
@description('Resource name prefix')
8+
param namePrefix string
9+
10+
@description('Datadog API key')
11+
@secure()
12+
param datadogApiKey string
13+
14+
@description('Datadog site (e.g. datadoghq.com, datadoghq.eu)')
15+
param datadogSite string
16+
17+
@description('Resource ID of the ACI subnet')
18+
param aciSubnetId string
19+
20+
@description('Enable CanNotDelete lock')
21+
param enableLocks bool = true
22+
23+
@description('Custom tags to merge with default tags')
24+
param customTags object = {}
25+
26+
var tags = union({ 'dd-component': 'apim-callout' }, customTags)
27+
28+
resource agentContainerGroup 'Microsoft.ContainerInstance/containerGroups@2023-05-01' = {
29+
name: '${namePrefix}-agent'
30+
location: location
31+
tags: tags
32+
properties: {
33+
osType: 'Linux'
34+
restartPolicy: 'Always'
35+
ipAddress: {
36+
type: 'Private'
37+
ports: [
38+
{
39+
port: 8126
40+
protocol: 'TCP'
41+
}
42+
]
43+
}
44+
subnetIds: [
45+
{
46+
id: aciSubnetId
47+
}
48+
]
49+
containers: [
50+
{
51+
name: 'datadog-agent'
52+
properties: {
53+
image: 'datadog/agent:latest'
54+
resources: {
55+
requests: {
56+
cpu: 1
57+
memoryInGB: 2
58+
}
59+
}
60+
ports: [
61+
{
62+
port: 8126
63+
protocol: 'TCP'
64+
}
65+
]
66+
environmentVariables: [
67+
{
68+
name: 'DD_API_KEY'
69+
secureValue: datadogApiKey
70+
}
71+
{
72+
name: 'DD_SITE'
73+
value: datadogSite
74+
}
75+
{
76+
name: 'DD_APM_ENABLED'
77+
value: 'true'
78+
}
79+
{
80+
name: 'DD_APM_NON_LOCAL_TRAFFIC'
81+
value: 'true'
82+
}
83+
{
84+
name: 'DD_REMOTE_CONFIGURATION_ENABLED'
85+
value: 'true'
86+
}
87+
{
88+
name: 'DD_LOGS_ENABLED'
89+
value: 'false'
90+
}
91+
{
92+
name: 'DD_PROCESS_AGENT_ENABLED'
93+
value: 'false'
94+
}
95+
{
96+
name: 'DD_SYSTEM_PROBE_ENABLED'
97+
value: 'false'
98+
}
99+
{
100+
name: 'DD_APM_RECEIVER_PORT'
101+
value: '8126'
102+
}
103+
{
104+
name: 'DD_HOSTNAME'
105+
value: '${namePrefix}-agent-apim-callout'
106+
}
107+
]
108+
}
109+
}
110+
]
111+
}
112+
}
113+
114+
resource lock 'Microsoft.Authorization/locks@2020-05-01' = if (enableLocks) {
115+
name: '${namePrefix}-agent-lock'
116+
scope: agentContainerGroup
117+
properties: {
118+
level: 'CanNotDelete'
119+
notes: 'Prevents accidental deletion of the Datadog Agent container group'
120+
}
121+
}
122+
123+
output agentIp string = agentContainerGroup.properties.ipAddress.ip

0 commit comments

Comments
 (0)