@rulebricks/cli 2.1.6 → 2.3.1

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.
Files changed (114) hide show
  1. package/README.md +75 -14
  2. package/cluster-setup/aws/README.md +123 -0
  3. package/cluster-setup/aws/check-aws-access.sh +242 -0
  4. package/cluster-setup/aws/parameters.json +13 -0
  5. package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
  6. package/cluster-setup/azure/README.md +141 -0
  7. package/cluster-setup/azure/check-aks-prereqs.sh +276 -0
  8. package/cluster-setup/azure/parameters.json +30 -0
  9. package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
  10. package/cluster-setup/gcp/README.md +189 -0
  11. package/cluster-setup/gcp/check-gke-prereqs.sh +260 -0
  12. package/dist/commands/backup.d.ts +5 -0
  13. package/dist/commands/backup.js +104 -0
  14. package/dist/commands/deploy.d.ts +3 -1
  15. package/dist/commands/deploy.js +226 -326
  16. package/dist/commands/destroy.d.ts +1 -1
  17. package/dist/commands/destroy.js +73 -123
  18. package/dist/commands/init.d.ts +5 -1
  19. package/dist/commands/init.js +78 -47
  20. package/dist/commands/list.d.ts +1 -0
  21. package/dist/commands/list.js +74 -0
  22. package/dist/commands/open.d.ts +1 -1
  23. package/dist/commands/open.js +4 -12
  24. package/dist/commands/redeploy.d.ts +6 -0
  25. package/dist/commands/redeploy.js +310 -0
  26. package/dist/commands/restore.d.ts +5 -0
  27. package/dist/commands/restore.js +338 -0
  28. package/dist/commands/status.js +62 -49
  29. package/dist/commands/upgrade.js +74 -51
  30. package/dist/components/DNSWaitScreen.d.ts +5 -1
  31. package/dist/components/DNSWaitScreen.js +47 -41
  32. package/dist/components/Wizard/WizardContext.d.ts +174 -29
  33. package/dist/components/Wizard/WizardContext.js +896 -91
  34. package/dist/components/Wizard/steps/CloudProviderStep.js +192 -102
  35. package/dist/components/Wizard/steps/DomainStep.js +5 -24
  36. package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
  37. package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
  38. package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
  39. package/dist/components/Wizard/steps/FeatureConfigStep.js +959 -248
  40. package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
  41. package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
  42. package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
  43. package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
  44. package/dist/components/Wizard/steps/ReviewStep.js +56 -7
  45. package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
  46. package/dist/components/Wizard/steps/StorageStep.js +592 -0
  47. package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
  48. package/dist/components/Wizard/steps/VersionStep.js +45 -23
  49. package/dist/components/Wizard/steps/index.d.ts +3 -3
  50. package/dist/components/Wizard/steps/index.js +3 -3
  51. package/dist/components/common/CommandApproval.d.ts +12 -0
  52. package/dist/components/common/CommandApproval.js +91 -0
  53. package/dist/components/common/DeploymentPicker.d.ts +14 -0
  54. package/dist/components/common/DeploymentPicker.js +16 -0
  55. package/dist/components/common/index.d.ts +2 -0
  56. package/dist/components/common/index.js +2 -0
  57. package/dist/index.js +94 -62
  58. package/dist/lib/cloudCli.d.ts +134 -63
  59. package/dist/lib/cloudCli.js +512 -220
  60. package/dist/lib/clusterSetupDefaults.d.ts +30 -0
  61. package/dist/lib/clusterSetupDefaults.js +64 -0
  62. package/dist/lib/commandApproval.d.ts +26 -0
  63. package/dist/lib/commandApproval.js +114 -0
  64. package/dist/lib/config.d.ts +12 -10
  65. package/dist/lib/config.js +91 -33
  66. package/dist/lib/configFixtures.d.ts +5 -0
  67. package/dist/lib/configFixtures.js +513 -0
  68. package/dist/lib/deploymentHealth.d.ts +32 -0
  69. package/dist/lib/deploymentHealth.js +157 -0
  70. package/dist/lib/dns.d.ts +1 -1
  71. package/dist/lib/dns.js +19 -1
  72. package/dist/lib/dns.test.d.ts +1 -0
  73. package/dist/lib/dns.test.js +27 -0
  74. package/dist/lib/dockerHub.d.ts +12 -1
  75. package/dist/lib/dockerHub.js +18 -8
  76. package/dist/lib/helm.d.ts +4 -0
  77. package/dist/lib/helm.js +16 -0
  78. package/dist/lib/helmValues.d.ts +25 -0
  79. package/dist/lib/helmValues.js +1937 -259
  80. package/dist/lib/helmValues.test.d.ts +1 -0
  81. package/dist/lib/helmValues.test.js +966 -0
  82. package/dist/lib/htpasswd.d.ts +1 -0
  83. package/dist/lib/htpasswd.js +15 -0
  84. package/dist/lib/kubernetes.d.ts +126 -13
  85. package/dist/lib/kubernetes.js +624 -134
  86. package/dist/lib/secrets.d.ts +23 -0
  87. package/dist/lib/secrets.js +158 -0
  88. package/dist/lib/validateValues.d.ts +31 -0
  89. package/dist/lib/validateValues.js +253 -0
  90. package/dist/lib/versions.d.ts +82 -11
  91. package/dist/lib/versions.js +131 -31
  92. package/dist/lib/versions.test.d.ts +1 -0
  93. package/dist/lib/versions.test.js +81 -0
  94. package/dist/lib/wizardSteps.d.ts +14 -0
  95. package/dist/lib/wizardSteps.js +23 -0
  96. package/dist/lib/workloadIdentity.d.ts +26 -0
  97. package/dist/lib/workloadIdentity.js +323 -0
  98. package/dist/lib/workloadIdentity.test.d.ts +1 -0
  99. package/dist/lib/workloadIdentity.test.js +57 -0
  100. package/dist/types/index.d.ts +2152 -95
  101. package/dist/types/index.js +554 -286
  102. package/package.json +10 -4
  103. package/schema/values.schema.json +1934 -0
  104. package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
  105. package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
  106. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
  107. package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
  108. package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
  109. package/dist/components/Wizard/steps/TierStep.js +0 -29
  110. package/dist/lib/terraform.d.ts +0 -66
  111. package/dist/lib/terraform.js +0 -754
  112. package/terraform/aws/main.tf +0 -355
  113. package/terraform/azure/main.tf +0 -371
  114. package/terraform/gcp/main.tf +0 -407
@@ -0,0 +1,546 @@
1
+ targetScope = 'resourceGroup'
2
+
3
+ @description('Name of the AKS cluster.')
4
+ param clusterName string = 'rulebricks-cluster'
5
+
6
+ @description('Azure region for all resources.')
7
+ param location string = resourceGroup().location
8
+
9
+ @description('AKS Kubernetes version.')
10
+ param kubernetesVersion string = '1.34'
11
+
12
+ @description('Number of nodes in the default node pool.')
13
+ param nodeCount int = 2
14
+
15
+ @description('Maximum number of nodes in the default (core) pool. Core services need only 2-4 small nodes; burst capacity lives in the dedicated burst pool, so keeping this ceiling low also steers the autoscaler toward the burst pool when the worker fleet scales out.')
16
+ param maxNodeCount int = 4
17
+
18
+ @description('VM size for the default node pool.')
19
+ param nodeVmSize string = 'Standard_F4as_v6'
20
+
21
+ @description('Maximum pods per node in the default node pool.')
22
+ @minValue(10)
23
+ @maxValue(250)
24
+ param maxPods int = 110
25
+
26
+ @description('OS disk size in GB for the default node pool. 64+ keeps image churn and container ephemeral usage safely under the kubelet disk-pressure eviction threshold (~85%); 30GB disks ran at 65-82% under load.')
27
+ @minValue(30)
28
+ @maxValue(2048)
29
+ param osDiskSizeGB int = 64
30
+
31
+ @description('OS disk type for the default node pool.')
32
+ @allowed([
33
+ 'Managed'
34
+ 'Ephemeral'
35
+ ])
36
+ param osDiskType string = 'Managed'
37
+
38
+ @description('Provision the dedicated burst worker pool: one large VM, scale 0->burstMaxCount, Deallocate scale-down (parked at disk-only cost with images cached, ~30-60s resume). Labeled and tainted rulebricks.com/pool=burst; the Rulebricks chart makes workers tolerate and softly prefer it out of the box.')
39
+ param enableBurstPool bool = true
40
+
41
+ @description('VM size for the burst worker pool. Default 16 vCPU (the Fas_v6 family has no 24-vCPU size): 2x4 vCPU core floor + 16 = 24 vCPU running steady-state at full burst, and exactly 32 vCPU even with the core pool at its 4-node max - sized to a 32-vCPU family quota. One big node beats many small ones for bang-bang scaling: one start event, one image set, no straggler tail.')
42
+ param burstVmSize string = 'Standard_F16as_v6'
43
+
44
+ @description('Maximum nodes in the burst pool.')
45
+ param burstMaxCount int = 1
46
+
47
+ // This template is deployment-independent: it provisions the shared identity,
48
+ // storage, and Azure Monitor resources but NOT the federated identity
49
+ // credentials, which are namespace-scoped. The Rulebricks CLI creates the
50
+ // per-deployment federated credentials at `rulebricks deploy` time (it knows the
51
+ // namespace and ServiceAccounts), so one cluster can host many deployments
52
+ // without re-running this template.
53
+
54
+ @description('Enable a user-assigned identity and federated credential for external-dns with Azure DNS.')
55
+ param enableExternalDns bool = false
56
+
57
+ @description('Resource group containing the Azure DNS zone. Required when enableExternalDns is true.')
58
+ param dnsZoneResourceGroup string = ''
59
+
60
+ @description('Namespace for the external-dns federated credential. Only used when enableExternalDns is true; set it to the CLI deployment namespace (rulebricks-<deploymentName>). The core vector/backup/prometheus credentials are created by the CLI at deploy time and do not use this.')
61
+ param rulebricksNamespace string = 'rulebricks'
62
+
63
+ // ----------------------------------------------------------------------------
64
+ // OBJECT STORAGE (all Rulebricks data)
65
+ //
66
+ // One identity, one storage account, one container. Decision logs and database
67
+ // backups are just key prefixes within it (decision-logs/ and db-backups/), so
68
+ // adding more data types later never means another identity or container.
69
+ //
70
+ // createStorage = true -> this template provisions the storage account and the
71
+ // single data container (turnkey; deterministic
72
+ // globally-unique account name via uniqueString()).
73
+ // createStorage = false -> bring your own: set existingStorageAccountName.
74
+ // ----------------------------------------------------------------------------
75
+ @description('Provision a storage account + the single data container in this template (turnkey). Set false to bring your own.')
76
+ param createStorage bool = true
77
+
78
+ @description('BYO: existing storage account for all Rulebricks data. Required when createStorage is false and decision-log or backup export is enabled.')
79
+ param existingStorageAccountName string = ''
80
+
81
+ @description('Blob container holding all Rulebricks data (decision-logs/ and db-backups/ prefixes).')
82
+ param dataContainerName string = '${clusterName}-data'
83
+
84
+ @description('Enable Vector decision-log export to Blob (federates the Rulebricks identity to the vector ServiceAccount).')
85
+ param enableDecisionLogExport bool = false
86
+
87
+ @description('Enable database backup export to Blob (federates the Rulebricks identity to the backup ServiceAccount).')
88
+ param enableBackupExport bool = false
89
+
90
+ // ----------------------------------------------------------------------------
91
+ // METRICS (Prometheus remote write -> Azure Monitor managed Prometheus)
92
+ //
93
+ // createMonitorWorkspace = true -> provision Azure Monitor workspace + an
94
+ // explicit Data Collection Endpoint + Data Collection Rule in THIS resource
95
+ // group, so the Monitoring Metrics Publisher role can be scoped to a DCR we
96
+ // own and name. (Creating only the workspace would auto-spawn a DCR/DCE in a
97
+ // separate MA_<name>_<region>_managed RG that this template can't cleanly
98
+ // scope a role to.)
99
+ // createMonitorWorkspace = false -> bring your own: set existingDataCollectionRuleId.
100
+ // ----------------------------------------------------------------------------
101
+ @description('Provision Azure Monitor workspace + DCE + DCR in this template (turnkey). Set false to bring your own DCR.')
102
+ param createMonitorWorkspace bool = true
103
+
104
+ @description('BYO: resource ID of an existing DCR associated with an Azure Monitor workspace. Required when createMonitorWorkspace is false and metrics remote write is enabled. The Monitoring Metrics Publisher role is assigned on this DCR.')
105
+ param existingDataCollectionRuleId string = ''
106
+
107
+ @description('Enable identity + role for Prometheus remote write to Azure Monitor.')
108
+ param enableMetricsRemoteWrite bool = false
109
+
110
+ var networkContributorRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
111
+ var dnsZoneContributorRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')
112
+ var storageBlobDataContributorRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')
113
+ var monitoringMetricsPublisherRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3913510d-42f4-4e42-8a64-420c390055eb')
114
+
115
+ // Deterministic, globally-unique storage account name (3-24 chars, lowercase alphanumeric).
116
+ var generatedStorageAccountName = take('rb${uniqueString(resourceGroup().id, clusterName)}', 24)
117
+ var effectiveStorageAccountName = createStorage ? generatedStorageAccountName : existingStorageAccountName
118
+ var enableBlobAccess = enableDecisionLogExport || enableBackupExport
119
+ var monitorWorkspaceName = '${clusterName}-amw'
120
+ var dceName = '${clusterName}-dce'
121
+ var dcrName = '${clusterName}-dcr'
122
+
123
+ resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = {
124
+ name: '${clusterName}-vnet'
125
+ location: location
126
+ tags: {
127
+ Environment: 'rulebricks'
128
+ }
129
+ properties: {
130
+ addressSpace: {
131
+ addressPrefixes: [
132
+ '10.0.0.0/8'
133
+ ]
134
+ }
135
+ subnets: [
136
+ {
137
+ name: 'aks-subnet'
138
+ properties: {
139
+ addressPrefix: '10.240.0.0/16'
140
+ }
141
+ }
142
+ ]
143
+ }
144
+ }
145
+
146
+ resource nsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
147
+ name: '${clusterName}-nsg'
148
+ location: location
149
+ tags: {
150
+ Environment: 'rulebricks'
151
+ }
152
+ properties: {
153
+ securityRules: [
154
+ {
155
+ name: 'AllowVNetInbound'
156
+ properties: {
157
+ priority: 100
158
+ direction: 'Inbound'
159
+ access: 'Allow'
160
+ protocol: '*'
161
+ sourcePortRange: '*'
162
+ destinationPortRange: '*'
163
+ sourceAddressPrefix: 'VirtualNetwork'
164
+ destinationAddressPrefix: 'VirtualNetwork'
165
+ }
166
+ }
167
+ {
168
+ name: 'AllowVNetOutbound'
169
+ properties: {
170
+ priority: 100
171
+ direction: 'Outbound'
172
+ access: 'Allow'
173
+ protocol: '*'
174
+ sourcePortRange: '*'
175
+ destinationPortRange: '*'
176
+ sourceAddressPrefix: 'VirtualNetwork'
177
+ destinationAddressPrefix: 'VirtualNetwork'
178
+ }
179
+ }
180
+ {
181
+ name: 'AllowHTTPInbound'
182
+ properties: {
183
+ priority: 110
184
+ direction: 'Inbound'
185
+ access: 'Allow'
186
+ protocol: 'Tcp'
187
+ sourcePortRange: '*'
188
+ destinationPortRange: '80'
189
+ sourceAddressPrefix: 'Internet'
190
+ destinationAddressPrefix: '*'
191
+ }
192
+ }
193
+ {
194
+ name: 'AllowHTTPSInbound'
195
+ properties: {
196
+ priority: 120
197
+ direction: 'Inbound'
198
+ access: 'Allow'
199
+ protocol: 'Tcp'
200
+ sourcePortRange: '*'
201
+ destinationPortRange: '443'
202
+ sourceAddressPrefix: 'Internet'
203
+ destinationAddressPrefix: '*'
204
+ }
205
+ }
206
+ ]
207
+ }
208
+ }
209
+
210
+ resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = {
211
+ parent: vnet
212
+ name: 'aks-subnet'
213
+ properties: {
214
+ addressPrefix: '10.240.0.0/16'
215
+ networkSecurityGroup: {
216
+ id: nsg.id
217
+ }
218
+ }
219
+ }
220
+
221
+ resource aksIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
222
+ name: '${clusterName}-identity'
223
+ location: location
224
+ }
225
+
226
+ resource aksNetworkRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
227
+ name: guid(vnet.id, aksIdentity.id, 'Network Contributor')
228
+ scope: vnet
229
+ properties: {
230
+ roleDefinitionId: networkContributorRoleId
231
+ principalId: aksIdentity.properties.principalId
232
+ principalType: 'ServicePrincipal'
233
+ }
234
+ }
235
+
236
+ resource aks 'Microsoft.ContainerService/managedClusters@2024-05-01' = {
237
+ name: clusterName
238
+ location: location
239
+ tags: {
240
+ Environment: 'rulebricks'
241
+ }
242
+ identity: {
243
+ type: 'UserAssigned'
244
+ userAssignedIdentities: {
245
+ '${aksIdentity.id}': {}
246
+ }
247
+ }
248
+ properties: {
249
+ dnsPrefix: clusterName
250
+ kubernetesVersion: kubernetesVersion
251
+ agentPoolProfiles: concat(
252
+ [
253
+ {
254
+ name: 'default'
255
+ count: nodeCount
256
+ enableAutoScaling: true
257
+ minCount: nodeCount
258
+ maxCount: maxNodeCount
259
+ vmSize: nodeVmSize
260
+ maxPods: maxPods
261
+ osDiskSizeGB: osDiskSizeGB
262
+ osDiskType: osDiskType
263
+ osType: 'Linux'
264
+ type: 'VirtualMachineScaleSets'
265
+ mode: 'System'
266
+ // Scale-down parks VMs (stopped, disk-only cost) instead of
267
+ // deleting them; resume is ~30-60s with container images cached.
268
+ scaleDownMode: 'Deallocate'
269
+ vnetSubnetID: subnet.id
270
+ }
271
+ ],
272
+ enableBurstPool
273
+ ? [
274
+ {
275
+ // Dedicated burst capacity for the stateless worker fleet:
276
+ // one large VM that parks (Deallocate) between bursts. The
277
+ // taint keeps everything except workers off it; the label is
278
+ // what the chart's soft node affinity targets. First-ever
279
+ // burst cold-provisions (~2-4 min); thereafter resume is
280
+ // ~30-60s with images cached.
281
+ name: 'burst'
282
+ count: 0
283
+ enableAutoScaling: true
284
+ minCount: 0
285
+ maxCount: burstMaxCount
286
+ vmSize: burstVmSize
287
+ maxPods: maxPods
288
+ osDiskSizeGB: osDiskSizeGB
289
+ osDiskType: osDiskType
290
+ osType: 'Linux'
291
+ type: 'VirtualMachineScaleSets'
292
+ mode: 'User'
293
+ scaleDownMode: 'Deallocate'
294
+ nodeLabels: {
295
+ 'rulebricks.com/pool': 'burst'
296
+ }
297
+ nodeTaints: [
298
+ 'rulebricks.com/pool=burst:NoSchedule'
299
+ ]
300
+ vnetSubnetID: subnet.id
301
+ }
302
+ ]
303
+ : [],
304
+ )
305
+ // Tuned for bursty traffic: detect pending pods quickly and pick the
306
+ // node pool that wastes the least capacity. With the core pool capped at
307
+ // maxNodeCount (4), a scaled-out worker fleet overflows to the burst
308
+ // pool within 1-2 autoscaler iterations. Scale-down keeps defaults -
309
+ // gaps between bursts should not thrash nodes.
310
+ autoScalerProfile: {
311
+ 'scan-interval': '10s'
312
+ expander: 'least-waste'
313
+ }
314
+ networkProfile: {
315
+ networkPlugin: 'azure'
316
+ networkPolicy: 'calico'
317
+ loadBalancerSku: 'standard'
318
+ serviceCidr: '10.0.0.0/16'
319
+ dnsServiceIP: '10.0.0.10'
320
+ }
321
+ oidcIssuerProfile: {
322
+ enabled: true
323
+ }
324
+ securityProfile: {
325
+ workloadIdentity: {
326
+ enabled: true
327
+ }
328
+ }
329
+ storageProfile: {
330
+ diskCSIDriver: {
331
+ enabled: true
332
+ }
333
+ fileCSIDriver: {
334
+ enabled: true
335
+ }
336
+ }
337
+ }
338
+ dependsOn: [
339
+ aksNetworkRole
340
+ ]
341
+ }
342
+
343
+ resource externalDnsIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = if (enableExternalDns) {
344
+ name: '${clusterName}-external-dns'
345
+ location: location
346
+ }
347
+
348
+ resource externalDnsRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableExternalDns && (empty(dnsZoneResourceGroup) || dnsZoneResourceGroup == resourceGroup().name)) {
349
+ name: guid(resourceGroup().id, externalDnsIdentity!.id, 'DNS Zone Contributor')
350
+ scope: resourceGroup()
351
+ properties: {
352
+ roleDefinitionId: dnsZoneContributorRoleId
353
+ principalId: externalDnsIdentity!.properties.principalId
354
+ principalType: 'ServicePrincipal'
355
+ }
356
+ }
357
+
358
+ resource externalDnsFederatedCredential 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = if (enableExternalDns) {
359
+ parent: externalDnsIdentity
360
+ name: 'external-dns'
361
+ properties: {
362
+ issuer: aks.properties.oidcIssuerProfile.issuerURL
363
+ subject: 'system:serviceaccount:${rulebricksNamespace}:external-dns'
364
+ audiences: [
365
+ 'api://AzureADTokenExchange'
366
+ ]
367
+ }
368
+ }
369
+
370
+ // ----------------------------------------------------------------------------
371
+ // STORAGE ACCOUNT + CONTAINERS (created only when createStorage = true)
372
+ // ----------------------------------------------------------------------------
373
+ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = if (createStorage) {
374
+ name: generatedStorageAccountName
375
+ location: location
376
+ sku: {
377
+ name: 'Standard_LRS'
378
+ }
379
+ kind: 'StorageV2'
380
+ properties: {
381
+ accessTier: 'Hot'
382
+ allowBlobPublicAccess: false
383
+ minimumTlsVersion: 'TLS1_2'
384
+ supportsHttpsTrafficOnly: true
385
+ }
386
+ }
387
+
388
+ resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = if (createStorage) {
389
+ parent: storageAccount
390
+ name: 'default'
391
+ }
392
+
393
+ resource dataContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if (createStorage && enableBlobAccess) {
394
+ parent: blobService
395
+ name: dataContainerName
396
+ properties: {
397
+ publicAccess: 'None'
398
+ }
399
+ }
400
+
401
+ // Existing-account reference for BYO (createStorage = false)
402
+ resource existingStorageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (!createStorage && enableBlobAccess && !empty(effectiveStorageAccountName)) {
403
+ name: effectiveStorageAccountName
404
+ }
405
+
406
+ // ----------------------------------------------------------------------------
407
+ // RULEBRICKS WORKLOAD IDENTITY (single identity for all data paths)
408
+ // Identity: ${clusterName}-rulebricks
409
+ // Roles: Storage Blob Data Contributor (logs + backups) on the storage
410
+ // account, and Monitoring Metrics Publisher on the DCR (below).
411
+ // Federation to the vector / backup / prometheus ServiceAccounts is created by
412
+ // the Rulebricks CLI at deploy time (namespace-scoped), so this one identity can
413
+ // back any number of deployments on the cluster.
414
+ // ----------------------------------------------------------------------------
415
+ resource rulebricksIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
416
+ name: '${clusterName}-rulebricks'
417
+ location: location
418
+ }
419
+
420
+ resource blobRoleCreated 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableBlobAccess && createStorage) {
421
+ name: guid(storageAccount.id, rulebricksIdentity.id, 'Storage Blob Data Contributor')
422
+ scope: storageAccount
423
+ properties: {
424
+ roleDefinitionId: storageBlobDataContributorRoleId
425
+ principalId: rulebricksIdentity.properties.principalId
426
+ principalType: 'ServicePrincipal'
427
+ }
428
+ }
429
+
430
+ resource blobRoleByo 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableBlobAccess && !createStorage && !empty(effectiveStorageAccountName)) {
431
+ name: guid(existingStorageAccount.id, rulebricksIdentity.id, 'Storage Blob Data Contributor')
432
+ scope: existingStorageAccount
433
+ properties: {
434
+ roleDefinitionId: storageBlobDataContributorRoleId
435
+ principalId: rulebricksIdentity.properties.principalId
436
+ principalType: 'ServicePrincipal'
437
+ }
438
+ }
439
+
440
+ // Federated credentials for vector / backup / prometheus are created by the
441
+ // Rulebricks CLI at deploy time (they are namespace-scoped). This template only
442
+ // grants the identity its roles.
443
+
444
+ // ----------------------------------------------------------------------------
445
+ // METRICS PATH (same Rulebricks identity)
446
+ // Federated: created by the CLI at deploy time for SA ${rulebricksNamespace}:prometheus
447
+ // Role: Monitoring Metrics Publisher on the DCR
448
+ // Note: role takes ~30 min to propagate; expect HTTP 403 until then.
449
+ //
450
+ // createMonitorWorkspace = true provisions AMW + DCE + DCR here so the role is
451
+ // scoped to a DCR we own. The remote-write endpoint for Prometheus is the DCE
452
+ // metricsIngestion endpoint (surfaced as an output).
453
+ // ----------------------------------------------------------------------------
454
+ resource monitorWorkspace 'Microsoft.Monitor/accounts@2023-04-03' = if (enableMetricsRemoteWrite && createMonitorWorkspace) {
455
+ name: monitorWorkspaceName
456
+ location: location
457
+ }
458
+
459
+ resource dce 'Microsoft.Insights/dataCollectionEndpoints@2023-03-11' = if (enableMetricsRemoteWrite && createMonitorWorkspace) {
460
+ name: dceName
461
+ location: location
462
+ kind: 'Linux'
463
+ properties: {}
464
+ }
465
+
466
+ resource dcr 'Microsoft.Insights/dataCollectionRules@2023-03-11' = if (enableMetricsRemoteWrite && createMonitorWorkspace) {
467
+ name: dcrName
468
+ location: location
469
+ kind: 'Linux'
470
+ properties: {
471
+ dataCollectionEndpointId: dce.id
472
+ dataSources: {
473
+ prometheusForwarder: [
474
+ {
475
+ name: 'PrometheusDataSource'
476
+ streams: [
477
+ 'Microsoft-PrometheusMetrics'
478
+ ]
479
+ labelIncludeFilter: {}
480
+ }
481
+ ]
482
+ }
483
+ destinations: {
484
+ monitoringAccounts: [
485
+ {
486
+ accountResourceId: monitorWorkspace.id
487
+ name: 'MonitoringAccountDestination'
488
+ }
489
+ ]
490
+ }
491
+ dataFlows: [
492
+ {
493
+ streams: [
494
+ 'Microsoft-PrometheusMetrics'
495
+ ]
496
+ destinations: [
497
+ 'MonitoringAccountDestination'
498
+ ]
499
+ }
500
+ ]
501
+ }
502
+ }
503
+
504
+ // BYO DCR reference (createMonitorWorkspace = false)
505
+ resource existingDcr 'Microsoft.Insights/dataCollectionRules@2023-03-11' existing = if (enableMetricsRemoteWrite && !createMonitorWorkspace && !empty(existingDataCollectionRuleId)) {
506
+ name: last(split(existingDataCollectionRuleId, '/'))
507
+ }
508
+
509
+ resource metricsPublisherRoleCreated 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableMetricsRemoteWrite && createMonitorWorkspace) {
510
+ name: guid(dcr.id, rulebricksIdentity.id, 'Monitoring Metrics Publisher')
511
+ scope: dcr
512
+ properties: {
513
+ roleDefinitionId: monitoringMetricsPublisherRoleId
514
+ principalId: rulebricksIdentity.properties.principalId
515
+ principalType: 'ServicePrincipal'
516
+ }
517
+ }
518
+
519
+ resource metricsPublisherRoleByo 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableMetricsRemoteWrite && !createMonitorWorkspace && !empty(existingDataCollectionRuleId)) {
520
+ name: guid(existingDataCollectionRuleId, rulebricksIdentity.id, 'Monitoring Metrics Publisher')
521
+ scope: existingDcr
522
+ properties: {
523
+ roleDefinitionId: monitoringMetricsPublisherRoleId
524
+ principalId: rulebricksIdentity.properties.principalId
525
+ principalType: 'ServicePrincipal'
526
+ }
527
+ }
528
+
529
+ output clusterName string = aks.name
530
+ output resourceGroupName string = resourceGroup().name
531
+ output location string = location
532
+ output kubeconfigCommand string = 'az aks get-credentials --name ${clusterName} --resource-group ${resourceGroup().name}'
533
+ output externalDnsClientId string = enableExternalDns ? externalDnsIdentity!.properties.clientId : ''
534
+
535
+ // Storage outputs. One identity + one account + one container back every data
536
+ // path; decision logs and backups are prefixes within the container.
537
+ output storageAccountName string = effectiveStorageAccountName
538
+ output dataContainer string = enableBlobAccess ? dataContainerName : ''
539
+ output rulebricksClientId string = rulebricksIdentity.properties.clientId
540
+
541
+ // Metrics outputs (Prometheus remote write uses the same rulebricksClientId).
542
+ // DCE ingestion endpoint + DCR immutableId. The Prometheus remote_write URL is:
543
+ // <dceMetricsIngestionEndpoint>/dataCollectionRules/<dcrImmutableId>/streams/Microsoft-PrometheusMetrics/api/v1/write?api-version=2023-04-24
544
+ output dceMetricsIngestionEndpoint string = (enableMetricsRemoteWrite && createMonitorWorkspace) ? dce!.properties.metricsIngestion.endpoint : ''
545
+ output dcrImmutableId string = (enableMetricsRemoteWrite && createMonitorWorkspace) ? dcr!.properties.immutableId : ''
546
+ output dataCollectionRuleId string = (enableMetricsRemoteWrite && createMonitorWorkspace) ? dcr!.id : existingDataCollectionRuleId