@intentius/chant-lexicon-azure 0.124.31 → 0.124.33

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 (144) hide show
  1. package/dist/actions/appservice.js +7 -0
  2. package/dist/actions/compute.js +9 -0
  3. package/dist/actions/container.js +14 -0
  4. package/dist/actions/identity.js +11 -0
  5. package/dist/actions/index.js +11 -0
  6. package/dist/actions/keyvault.js +12 -0
  7. package/dist/actions/network.js +9 -0
  8. package/dist/actions/sql.js +9 -0
  9. package/dist/actions/storage.js +15 -0
  10. package/dist/api/read-client.js +146 -0
  11. package/dist/codegen/docs-cli.js +2 -0
  12. package/dist/codegen/docs.js +83 -0
  13. package/dist/codegen/generate-cli.js +11 -0
  14. package/dist/codegen/generate-lexicon.js +77 -0
  15. package/dist/codegen/generate-typescript.js +163 -0
  16. package/dist/codegen/generate.js +106 -0
  17. package/dist/codegen/naming.js +139 -0
  18. package/dist/codegen/package.js +64 -0
  19. package/dist/composites/aks-cluster.js +68 -0
  20. package/dist/composites/app-service.js +62 -0
  21. package/dist/composites/application-gateway.js +107 -0
  22. package/dist/composites/catalog.js +856 -0
  23. package/dist/composites/container-instance.js +36 -0
  24. package/dist/composites/container-registry.js +62 -0
  25. package/dist/composites/cosmos-database.js +56 -0
  26. package/dist/composites/from-arm.js +47 -0
  27. package/dist/composites/function-app.js +57 -0
  28. package/dist/composites/index.js +16 -0
  29. package/dist/composites/keyvault.js +69 -0
  30. package/dist/composites/landing-zone.js +119 -0
  31. package/dist/composites/private-endpoint.js +61 -0
  32. package/dist/composites/redis-cache.js +21 -0
  33. package/dist/composites/service-bus-pipeline.js +54 -0
  34. package/dist/composites/sql-database.js +62 -0
  35. package/dist/composites/storage-account.js +59 -0
  36. package/dist/composites/vm-linux.js +139 -0
  37. package/dist/composites/vnet-default.js +102 -0
  38. package/dist/coverage.js +28 -0
  39. package/dist/deep-observe.js +356 -0
  40. package/dist/default-tags.js +50 -0
  41. package/dist/deploy-scopes.d.ts.map +1 -1
  42. package/dist/deploy-scopes.js +157 -0
  43. package/dist/describe-resources.js +187 -0
  44. package/dist/detect.js +17 -0
  45. package/dist/export-resources.js +108 -0
  46. package/dist/generated/index.d.ts.map +1 -1
  47. package/dist/generated/index.js +16598 -0
  48. package/dist/generated/runtime.js +4 -0
  49. package/dist/governance.js +144 -0
  50. package/dist/health.d.ts +42 -0
  51. package/dist/health.d.ts.map +1 -0
  52. package/dist/health.js +137 -0
  53. package/dist/import/generator.js +140 -0
  54. package/dist/import/live-export.js +39 -0
  55. package/dist/import/parser.js +192 -0
  56. package/dist/index.js +44 -0
  57. package/dist/integrity.json +5 -5
  58. package/dist/intrinsics.js +179 -0
  59. package/dist/lint/audit-catalog.js +36 -0
  60. package/dist/lint/audit-lineage.js +71 -0
  61. package/dist/lint/post-synth/arm-refs.d.ts +1 -0
  62. package/dist/lint/post-synth/arm-refs.d.ts.map +1 -1
  63. package/dist/lint/post-synth/arm-refs.js +96 -0
  64. package/dist/lint/post-synth/azr010.js +45 -0
  65. package/dist/lint/post-synth/azr011.js +43 -0
  66. package/dist/lint/post-synth/azr012.js +41 -0
  67. package/dist/lint/post-synth/azr013.js +50 -0
  68. package/dist/lint/post-synth/azr014.js +49 -0
  69. package/dist/lint/post-synth/azr015.js +66 -0
  70. package/dist/lint/post-synth/azr016.js +37 -0
  71. package/dist/lint/post-synth/azr017.js +37 -0
  72. package/dist/lint/post-synth/azr018.js +42 -0
  73. package/dist/lint/post-synth/azr019.js +41 -0
  74. package/dist/lint/post-synth/azr020.js +37 -0
  75. package/dist/lint/post-synth/azr021.js +36 -0
  76. package/dist/lint/post-synth/azr022.js +39 -0
  77. package/dist/lint/post-synth/azr023.js +39 -0
  78. package/dist/lint/post-synth/azr024.js +39 -0
  79. package/dist/lint/post-synth/azr025.js +37 -0
  80. package/dist/lint/post-synth/azr026.js +38 -0
  81. package/dist/lint/post-synth/azr027.js +37 -0
  82. package/dist/lint/post-synth/azr028.js +37 -0
  83. package/dist/lint/post-synth/azr029.js +38 -0
  84. package/dist/lint/post-synth/azr030.js +44 -0
  85. package/dist/lint/post-synth/azr031.js +36 -0
  86. package/dist/lint/post-synth/azr032.js +53 -0
  87. package/dist/lint/post-synth/azr033.js +35 -0
  88. package/dist/lint/post-synth/index.js +50 -0
  89. package/dist/lint/rules/hardcoded-location.js +82 -0
  90. package/dist/lint/rules/index.js +6 -0
  91. package/dist/lint/rules/nsg-wildcard.js +44 -0
  92. package/dist/lint/rules/storage-https.js +62 -0
  93. package/dist/lsp/completions.d.ts.map +1 -1
  94. package/dist/lsp/completions.js +18 -0
  95. package/dist/lsp/hover.d.ts.map +1 -1
  96. package/dist/lsp/hover.js +44 -0
  97. package/dist/manifest.json +1 -1
  98. package/dist/meta.json +6 -1
  99. package/dist/okf/types/containerApps.md +5 -0
  100. package/dist/op/activities/az-apply.js +563 -0
  101. package/dist/op/activities/azure.js +48 -0
  102. package/dist/op/activities/floci-az.js +37 -0
  103. package/dist/op/activities/index.js +10 -0
  104. package/dist/op/builders.js +70 -0
  105. package/dist/ownership.js +13 -0
  106. package/dist/package-cli.js +21 -0
  107. package/dist/parameter.js +13 -0
  108. package/dist/plugin.d.ts.map +1 -1
  109. package/dist/plugin.js +478 -0
  110. package/dist/pseudo.js +40 -0
  111. package/dist/rules/arm-refs.ts +5 -3
  112. package/dist/serializer.d.ts +7 -0
  113. package/dist/serializer.d.ts.map +1 -1
  114. package/dist/serializer.js +543 -0
  115. package/dist/spec/api-versions.js +130 -0
  116. package/dist/spec/fetch.js +196 -0
  117. package/dist/spec/parse.d.ts.map +1 -1
  118. package/dist/spec/parse.js +318 -0
  119. package/dist/taggable.js +34 -0
  120. package/dist/types/index.d.ts +5 -0
  121. package/dist/validate-cli.js +16 -0
  122. package/dist/validate.js +53 -0
  123. package/package.json +8 -8
  124. package/src/attributes.test.ts +17 -0
  125. package/src/codegen/generate-typescript.ts +1 -1
  126. package/src/codegen/generate.ts +1 -1
  127. package/src/codegen/package.ts +2 -2
  128. package/src/deploy-scopes.ts +2 -1
  129. package/src/generated/index.d.ts +5 -0
  130. package/src/generated/index.ts +1 -1
  131. package/src/generated/lexicon-azure.json +6 -1
  132. package/src/health.test.ts +66 -0
  133. package/src/health.ts +162 -0
  134. package/src/lint/post-synth/arm-refs.ts +5 -3
  135. package/src/lint/post-synth/post-synth.test.ts +1 -1
  136. package/src/lsp/completions.ts +2 -1
  137. package/src/lsp/hover.ts +2 -1
  138. package/src/plugin.ts +11 -2
  139. package/src/serializer-names.test.ts +83 -0
  140. package/src/serializer-outputs.test.ts +65 -0
  141. package/src/serializer.ts +112 -30
  142. package/src/spec/parse.test.ts +15 -0
  143. package/src/spec/parse.ts +10 -0
  144. package/src/taggable.ts +2 -2
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Runtime factory constructors — re-exported from core.
3
+ */
4
+ export { createResource, createProperty } from "@intentius/chant/runtime";
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Azure governance authoring: the desired-state config
3
+ * shape an external governance reconciler reconciles, and `landingZoneConfig()` — the
4
+ * typed authoring layer that emits it.
5
+ *
6
+ * This is deliberately NOT a composite. The evaluability rules (EVL002/004,
7
+ * #916/#952) require composites to declare a fixed set of resources, so a
8
+ * data-driven management-group tree walker cannot be one. The split mirrors
9
+ * the AWS/GCP slices (lexicons/{aws,gcp}/src/governance.ts): authoring emits
10
+ * config (this module, arbitrary cardinality); resources for greenfield
11
+ * bootstrap are the fixed-shape composites in `composites/landing-zone.ts`;
12
+ * the reconciler consumes only the config.
13
+ *
14
+ * Azure has no OrganizationRoot analog — the tenant and its root management
15
+ * group come from Entra ID, never from the resource API. Subscriptions the
16
+ * tree declares reconcile through Microsoft.Subscription/aliases (existing
17
+ * ids are placed, missing ones created from `billingScope`); aliases deploy
18
+ * at tenant scope only (#1545).
19
+ */
20
+ // ---------------------------------------------------------------------------
21
+ // Baseline guardrails (shared with the bootstrap composites)
22
+ // ---------------------------------------------------------------------------
23
+ export const DENY_CLASSIC_RESOURCES = {
24
+ description: "Classic (ASM) compute, network, and storage resources cannot be created.",
25
+ displayName: "Deny classic resources",
26
+ mode: "All",
27
+ policyRule: {
28
+ if: { field: "type", like: "Microsoft.Classic*" },
29
+ then: { effect: "deny" },
30
+ },
31
+ };
32
+ export const DENY_UNMANAGED_DISKS = {
33
+ description: "Virtual machines and scale sets must use managed disks.",
34
+ displayName: "Deny unmanaged disks",
35
+ mode: "All",
36
+ policyRule: {
37
+ if: {
38
+ anyOf: [
39
+ {
40
+ allOf: [
41
+ { field: "type", equals: "Microsoft.Compute/virtualMachines" },
42
+ { field: "Microsoft.Compute/virtualMachines/osDisk.uri", exists: "true" },
43
+ ],
44
+ },
45
+ {
46
+ allOf: [
47
+ { field: "type", equals: "Microsoft.Compute/virtualMachineScaleSets" },
48
+ {
49
+ anyOf: [
50
+ { field: "Microsoft.Compute/VirtualMachineScaleSets/osDisk.vhdContainers", exists: "true" },
51
+ { field: "Microsoft.Compute/VirtualMachineScaleSets/osdisk.imageUrl", exists: "true" },
52
+ ],
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ },
58
+ then: { effect: "deny" },
59
+ },
60
+ };
61
+ /** Allow resource creation only in `locations` ("global" is always excepted). */
62
+ export function locationRestriction(locations) {
63
+ return {
64
+ description: `Resources may only be created in [${locations.join(", ")}].`,
65
+ displayName: "Allowed locations",
66
+ mode: "Indexed",
67
+ policyRule: {
68
+ if: {
69
+ allOf: [
70
+ { field: "location", notIn: [...locations] },
71
+ { field: "location", notEquals: "global" },
72
+ ],
73
+ },
74
+ then: { effect: "deny" },
75
+ },
76
+ };
77
+ }
78
+ /**
79
+ * The built-in DeployIfNotExists policy "Configure Azure Activity logs to
80
+ * stream to specified Log Analytics workspace" — the activity-log audit
81
+ * sink is wired by assigning it, not by declaring diagnostic settings in
82
+ * every subscription (Microsoft.Insights/diagnosticSettings is not in the
83
+ * generated surface, and the assignment covers future subscriptions too).
84
+ */
85
+ export const ACTIVITY_LOG_TO_LOG_ANALYTICS_DEFINITION_ID = "/providers/Microsoft.Authorization/policyDefinitions/2465583e-4e78-4c15-b6be-a36cbc7c8b0f";
86
+ /**
87
+ * The recommended foundation management-group structure. Exported so
88
+ * callers can extend it rather than restate it.
89
+ */
90
+ export const FOUNDATION_MANAGEMENT_GROUPS = {
91
+ Security: {},
92
+ Infrastructure: {},
93
+ Sandbox: {},
94
+ Workloads: {},
95
+ };
96
+ /**
97
+ * Author the desired-state tree an external governance reconciler reconciles. Pure.
98
+ * Throws when the tree assigns a policy name with no definition; only
99
+ * policies the tree actually assigns are included in the output.
100
+ */
101
+ export function landingZoneConfig(props = {}) {
102
+ const foundation = props.foundation ?? true;
103
+ const policies = {
104
+ ...(foundation
105
+ ? { "deny-classic-resources": DENY_CLASSIC_RESOURCES, "deny-unmanaged-disks": DENY_UNMANAGED_DISKS }
106
+ : {}),
107
+ ...(props.allowedLocations?.length
108
+ ? { "location-restriction": locationRestriction(props.allowedLocations) }
109
+ : {}),
110
+ ...props.policies,
111
+ };
112
+ const managementGroups = { ...(foundation ? FOUNDATION_MANAGEMENT_GROUPS : {}), ...props.managementGroups };
113
+ const rootPolicies = [
114
+ ...(foundation ? ["deny-classic-resources", "deny-unmanaged-disks"] : []),
115
+ ...(props.allowedLocations?.length ? ["location-restriction"] : []),
116
+ ...(props.rootPolicies ?? []),
117
+ ];
118
+ const assigned = new Set(rootPolicies);
119
+ const collect = (tree) => {
120
+ for (const node of Object.values(tree)) {
121
+ for (const p of node.policies ?? [])
122
+ assigned.add(p);
123
+ if (node.children)
124
+ collect(node.children);
125
+ }
126
+ };
127
+ collect(managementGroups);
128
+ for (const name of assigned) {
129
+ if (!policies[name]) {
130
+ throw new Error(`landingZoneConfig: policy "${name}" is assigned but not defined in policies`);
131
+ }
132
+ }
133
+ return {
134
+ tenant: {
135
+ ...(rootPolicies.length ? { policies: rootPolicies } : {}),
136
+ ...(foundation ? { blockSubscriptionsLeavingTenant: true } : {}),
137
+ },
138
+ managementGroups,
139
+ policies: Object.fromEntries(Object.entries(policies).filter(([n]) => assigned.has(n))),
140
+ ...(props.activityLogWorkspaceId
141
+ ? { auditSinks: { activityLog: { workspaceId: props.activityLogWorkspaceId } } }
142
+ : {}),
143
+ };
144
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Runtime health for Azure (#4177): Container Apps.
3
+ *
4
+ * For each declared `Microsoft.App/containerApps`, GET the app (a 404 is
5
+ * absent), then its revisions, and judge the active ones by their running and
6
+ * health state. Same transport and endpoint rule as `describe-resources.ts`, so
7
+ * `AZURE_ENDPOINT_URL` (floci-az) answers.
8
+ *
9
+ * When the revisions list cannot be read but the app carries a
10
+ * `runningStatus` (newer API versions do), that answers instead, and the detail
11
+ * says so.
12
+ */
13
+ import type { HealthEntry, HealthReadOptions, HealthReadResult } from "@intentius/chant/lexicon";
14
+ import type { AzHttp } from "./op/activities/az-apply.js";
15
+ export declare const AZURE_HEALTH_TYPES: readonly ["Microsoft.App/containerApps"];
16
+ export interface AzureHealthOptions extends HealthReadOptions {
17
+ http?: AzHttp;
18
+ now?: () => Date;
19
+ }
20
+ /** The fields of one revision that decide health. */
21
+ export interface RevisionState {
22
+ name?: string;
23
+ properties?: {
24
+ active?: boolean;
25
+ runningState?: string;
26
+ healthState?: string;
27
+ replicas?: number;
28
+ trafficWeight?: number;
29
+ lastActiveTime?: string;
30
+ createdTime?: string;
31
+ };
32
+ }
33
+ /**
34
+ * The active revisions' verdict: unhealthy if none is active or any active one
35
+ * failed, degraded, stopped or reports `Unhealthy`; healthy if every active one
36
+ * is running and none is unhealthy; otherwise unknown (still provisioning).
37
+ */
38
+ export declare function containerAppEntry(revisions: RevisionState[], observedAt: string): HealthEntry;
39
+ /** An app's own `runningStatus`, for when its revisions cannot be listed. */
40
+ export declare function appRunningStatusEntry(runningStatus: string, observedAt: string): HealthEntry;
41
+ export declare function observeHealthAzure(options: AzureHealthOptions): Promise<HealthReadResult>;
42
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAoB,MAAM,0BAA0B,CAAC;AAGnH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAEvD,eAAO,MAAM,kBAAkB,YAAI,6BAA6B,CAAU,CAAC;AAK3E,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE;QACX,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAKD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAyB7F;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAS5F;AAiCD,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyC/F"}
package/dist/health.js ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Runtime health for Azure (#4177): Container Apps.
3
+ *
4
+ * For each declared `Microsoft.App/containerApps`, GET the app (a 404 is
5
+ * absent), then its revisions, and judge the active ones by their running and
6
+ * health state. Same transport and endpoint rule as `describe-resources.ts`, so
7
+ * `AZURE_ENDPOINT_URL` (floci-az) answers.
8
+ *
9
+ * When the revisions list cannot be read but the app carries a
10
+ * `runningStatus` (newer API versions do), that answers instead, and the detail
11
+ * says so.
12
+ */
13
+ import { armResourceReadUrl, AzureReadError, getResource, isNotFound } from "./api/read-client.js";
14
+ import { classifyArmFailure } from "./describe-resources.js";
15
+ export const AZURE_HEALTH_TYPES = ["Microsoft.App/containerApps"];
16
+ /** The Container Apps API version whose revisions carry `runningState` and `healthState`. */
17
+ const CONTAINER_APPS_API_VERSION = "2024-03-01";
18
+ const RUNNING = new Set(["running", "runningatmaxscale"]);
19
+ const DOWN = new Set(["failed", "degraded", "stopped"]);
20
+ /**
21
+ * The active revisions' verdict: unhealthy if none is active or any active one
22
+ * failed, degraded, stopped or reports `Unhealthy`; healthy if every active one
23
+ * is running and none is unhealthy; otherwise unknown (still provisioning).
24
+ */
25
+ export function containerAppEntry(revisions, observedAt) {
26
+ const type = "Microsoft.App/containerApps";
27
+ const active = revisions.filter((r) => r.properties?.active === true);
28
+ if (active.length === 0) {
29
+ return { type, state: "unhealthy", detail: `no active revision (${revisions.length} revision(s))`, observedAt };
30
+ }
31
+ const describe = (r) => {
32
+ const p = r.properties ?? {};
33
+ const bits = [p.runningState ?? "no running state"];
34
+ if (p.healthState && p.healthState !== "None")
35
+ bits.push(p.healthState);
36
+ if (typeof p.replicas === "number")
37
+ bits.push(`${p.replicas} replica(s)`);
38
+ if (typeof p.trafficWeight === "number")
39
+ bits.push(`${p.trafficWeight}% traffic`);
40
+ return `${r.name ?? "revision"} ${bits.join(", ")}`;
41
+ };
42
+ const detail = active.map(describe).join("; ");
43
+ const running = (r) => RUNNING.has((r.properties?.runningState ?? "").toLowerCase());
44
+ const down = (r) => DOWN.has((r.properties?.runningState ?? "").toLowerCase()) || r.properties?.healthState?.toLowerCase() === "unhealthy";
45
+ const checkedAt = active
46
+ .map((r) => r.properties?.lastActiveTime ?? r.properties?.createdTime)
47
+ .filter((t) => typeof t === "string")
48
+ .sort()
49
+ .pop();
50
+ const state = active.some(down) ? "unhealthy" : active.every(running) ? "healthy" : "unknown";
51
+ return { type, state, detail, observedAt, ...(checkedAt ? { checkedAt } : {}) };
52
+ }
53
+ /** An app's own `runningStatus`, for when its revisions cannot be listed. */
54
+ export function appRunningStatusEntry(runningStatus, observedAt) {
55
+ const lower = runningStatus.toLowerCase();
56
+ const state = RUNNING.has(lower) ? "healthy" : DOWN.has(lower) ? "unhealthy" : "unknown";
57
+ return {
58
+ type: "Microsoft.App/containerApps",
59
+ state,
60
+ detail: `app runningStatus ${runningStatus} (revisions not readable)`,
61
+ observedAt,
62
+ };
63
+ }
64
+ function hole(err) {
65
+ const { reason, detail } = classifyArmFailure(err);
66
+ return { type: "Microsoft.App/containerApps", reason, detail };
67
+ }
68
+ async function listRevisions(client, name) {
69
+ const http = client.http;
70
+ const url = armResourceReadUrl(client, "Microsoft.App/containerApps", `${name}/revisions`, CONTAINER_APPS_API_VERSION);
71
+ const res = await http("GET", url, undefined, client.signal);
72
+ if (res.status >= 300)
73
+ throw new AzureReadError(`revisions read failed with HTTP ${res.status}`, res.status);
74
+ let body;
75
+ try {
76
+ body = JSON.parse(res.text);
77
+ }
78
+ catch {
79
+ throw new AzureReadError("unparseable revisions list", res.status);
80
+ }
81
+ const value = body && typeof body === "object" ? body.value : undefined;
82
+ if (!Array.isArray(value))
83
+ throw new AzureReadError("unparseable revisions list", res.status);
84
+ return value;
85
+ }
86
+ const defaultHttp = async (method, url, body, signal) => {
87
+ const res = await fetch(url, {
88
+ method,
89
+ headers: body === undefined ? undefined : { "content-type": "application/json" },
90
+ body: body === undefined ? undefined : JSON.stringify(body),
91
+ signal,
92
+ });
93
+ return { status: res.status, text: await res.text() };
94
+ };
95
+ export async function observeHealthAzure(options) {
96
+ const observedAt = (options.now?.() ?? new Date()).toISOString();
97
+ const client = {
98
+ resourceGroup: options.environment,
99
+ ...(process.env.AZURE_ENDPOINT_URL ? { endpoint: process.env.AZURE_ENDPOINT_URL } : {}),
100
+ ...(process.env.AZURE_SUBSCRIPTION_ID ? { subscriptionId: process.env.AZURE_SUBSCRIPTION_ID } : {}),
101
+ http: options.http ?? defaultHttp,
102
+ };
103
+ const entities = {};
104
+ const unobserved = {};
105
+ const absent = [];
106
+ await Promise.all([...options.entities].map(async ([entityName, { entityType, props }]) => {
107
+ const name = props.name;
108
+ if (!name) {
109
+ unobserved[entityName] = { type: entityType, reason: "read-failed", detail: "declared entity has no name to query by" };
110
+ return;
111
+ }
112
+ let app;
113
+ try {
114
+ app = await getResource(client, entityType, name, CONTAINER_APPS_API_VERSION);
115
+ }
116
+ catch (err) {
117
+ if (isNotFound(err))
118
+ absent.push(entityName);
119
+ else
120
+ unobserved[entityName] = hole(err);
121
+ return;
122
+ }
123
+ try {
124
+ entities[entityName] = containerAppEntry(await listRevisions(client, name), observedAt);
125
+ }
126
+ catch (err) {
127
+ const runningStatus = app.properties?.runningStatus;
128
+ if (typeof runningStatus === "string" && runningStatus.length > 0) {
129
+ entities[entityName] = appRunningStatusEntry(runningStatus, observedAt);
130
+ }
131
+ else {
132
+ unobserved[entityName] = hole(err);
133
+ }
134
+ }
135
+ }));
136
+ return { entities, unobserved, absent };
137
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * ARM template → TypeScript code generator.
3
+ *
4
+ * Converts intermediate representation to idiomatic chant TypeScript.
5
+ */
6
+ import { loadLexiconRegistry } from "@intentius/chant/codegen/registry";
7
+ import { createRequire } from "module";
8
+ const require = createRequire(import.meta.url);
9
+ import { join } from "path";
10
+ /**
11
+ * TypeScript code generator for ARM templates.
12
+ */
13
+ export class ArmGenerator {
14
+ constructor() {
15
+ this.typeToClass = new Map();
16
+ // Was a silent catch, which left this map empty and made the import emit
17
+ // `// Unknown resource type: Microsoft.…` — indistinguishable from a
18
+ // genuine coverage gap in the lexicon. A missing registry now says so
19
+ // (#1367).
20
+ const meta = loadLexiconRegistry(join(import.meta.dirname, "../.."), "azure");
21
+ for (const [className, entry] of Object.entries(meta)) {
22
+ if (entry.kind === "resource" && !className.includes("_")) {
23
+ this.typeToClass.set(entry.resourceType, className);
24
+ }
25
+ }
26
+ }
27
+ generate(ir) {
28
+ const lines = [];
29
+ // Collect imports
30
+ const importedClasses = new Set();
31
+ const importedIntrinsics = new Set();
32
+ let needsAzure = false;
33
+ for (const resource of ir.resources) {
34
+ const cls = this.typeToClass.get(resource.type);
35
+ if (cls)
36
+ importedClasses.add(cls);
37
+ // Check for Azure pseudo-parameters in values
38
+ if (this.hasAzurePseudo(resource.properties)) {
39
+ needsAzure = true;
40
+ }
41
+ }
42
+ if (ir.parameters.length > 0) {
43
+ // Parameters would need a Parameter import from core
44
+ }
45
+ // Build import statement
46
+ const symbols = [...importedClasses].sort();
47
+ if (importedIntrinsics.size > 0) {
48
+ symbols.push(...[...importedIntrinsics].sort());
49
+ }
50
+ if (needsAzure) {
51
+ symbols.push("Azure");
52
+ }
53
+ if (symbols.length > 0) {
54
+ lines.push(`import { ${symbols.join(", ")} } from "@intentius/chant-lexicon-azure";`);
55
+ lines.push("");
56
+ }
57
+ // Generate resources
58
+ for (const resource of ir.resources) {
59
+ const cls = this.typeToClass.get(resource.type);
60
+ const varName = this.toVarName(resource.logicalId);
61
+ if (cls) {
62
+ lines.push(`export const ${varName} = new ${cls}(${this.generateProps(resource.properties, 1)});`);
63
+ }
64
+ else {
65
+ lines.push(`// Unknown resource type: ${resource.type}`);
66
+ lines.push(`// export const ${varName} = new ${resource.type}(${this.generateProps(resource.properties, 1)});`);
67
+ }
68
+ lines.push("");
69
+ }
70
+ return [
71
+ {
72
+ path: "main.ts",
73
+ content: lines.join("\n") + "\n",
74
+ },
75
+ ];
76
+ }
77
+ toVarName(name) {
78
+ // Convert to camelCase
79
+ return name.charAt(0).toLowerCase() + name.slice(1);
80
+ }
81
+ generateProps(props, depth) {
82
+ if (!props || Object.keys(props).length === 0)
83
+ return "{}";
84
+ const indent = " ".repeat(depth);
85
+ const innerIndent = " ".repeat(depth + 1);
86
+ const entries = [];
87
+ for (const [key, value] of Object.entries(props)) {
88
+ entries.push(`${innerIndent}${key}: ${this.generateValue(value, depth + 1)},`);
89
+ }
90
+ return `{\n${entries.join("\n")}\n${indent}}`;
91
+ }
92
+ generateValue(value, depth) {
93
+ if (value === null || value === undefined)
94
+ return "undefined";
95
+ if (typeof value === "string")
96
+ return JSON.stringify(value);
97
+ if (typeof value === "number" || typeof value === "boolean")
98
+ return String(value);
99
+ if (Array.isArray(value)) {
100
+ if (value.length === 0)
101
+ return "[]";
102
+ const items = value.map((v) => this.generateValue(v, depth + 1));
103
+ return `[${items.join(", ")}]`;
104
+ }
105
+ if (typeof value === "object") {
106
+ const obj = value;
107
+ // Handle intrinsic markers
108
+ if ("__intrinsic" in obj) {
109
+ return this.generateIntrinsic(obj);
110
+ }
111
+ return this.generateProps(obj, depth);
112
+ }
113
+ return String(value);
114
+ }
115
+ generateIntrinsic(obj) {
116
+ const name = obj.__intrinsic;
117
+ switch (name) {
118
+ case "ResourceGroup":
119
+ return `Azure.ResourceGroup${obj.property?.charAt(0).toUpperCase()}${obj.property?.slice(1)}`;
120
+ case "Subscription":
121
+ return `Azure.SubscriptionId`;
122
+ case "Ref":
123
+ return obj.name;
124
+ case "ResourceId":
125
+ return `ResourceId(${obj.args.map((a) => JSON.stringify(a)).join(", ")})`;
126
+ case "Reference":
127
+ return `Reference(${obj.args.map((a) => JSON.stringify(a)).join(", ")})`;
128
+ case "Concat":
129
+ return `Concat(${obj.args.map((a) => JSON.stringify(a)).join(", ")})`;
130
+ case "UniqueString":
131
+ return `UniqueString(${obj.args.map((a) => JSON.stringify(a)).join(", ")})`;
132
+ default:
133
+ return JSON.stringify(obj);
134
+ }
135
+ }
136
+ hasAzurePseudo(props) {
137
+ const json = JSON.stringify(props);
138
+ return json.includes("resourceGroup()") || json.includes("subscription()");
139
+ }
140
+ }
@@ -0,0 +1,39 @@
1
+ import { hasOwnershipMarker } from "@intentius/chant/ownership";
2
+ import { AZURE_TAG_OWNERSHIP_KEYS } from "../ownership.js";
3
+ import { ArmParser } from "./parser.js";
4
+ /**
5
+ * Map a live ARM template (from `az group export`) to full-fidelity export IR.
6
+ *
7
+ * `az group export --output json` returns an ARM template — either as a JSON
8
+ * object or, via the CLI, a JSON string. `ArmParser` already turns the template
9
+ * into `TemplateIR` — the same IR the static-import path uses — so the result
10
+ * feeds `ArmGenerator` (templateGenerator) unchanged. Pure: all I/O stays in
11
+ * the caller (`../export-resources`).
12
+ *
13
+ * Mirrors AWS `parseStackTemplate`. The one shape difference: ARM tags are a
14
+ * `Record<string,string>` map (not the `{Key,Value}[]` array CloudFormation
15
+ * uses), so the ownership filter reads `properties.tags` directly without
16
+ * `tagArrayToMap`, against the `azure-tag` channel (`chant-managed-by`).
17
+ */
18
+ export function parseExportedTemplate(templateBody, selector, owned) {
19
+ const content = typeof templateBody === "string" ? templateBody : JSON.stringify(templateBody);
20
+ const ir = new ArmParser().parse(content);
21
+ const hasSelector = selector && (selector.type !== undefined || selector.name !== undefined);
22
+ if (!hasSelector && !owned)
23
+ return ir;
24
+ return {
25
+ ...ir,
26
+ resources: ir.resources.filter((r) => {
27
+ if (selector?.type !== undefined && r.type !== selector.type)
28
+ return false;
29
+ if (selector?.name !== undefined && r.logicalId !== selector.name)
30
+ return false;
31
+ if (owned) {
32
+ const tags = r.properties.tags;
33
+ if (!hasOwnershipMarker(tags, AZURE_TAG_OWNERSHIP_KEYS))
34
+ return false;
35
+ }
36
+ return true;
37
+ }),
38
+ };
39
+ }