@intentius/chant-lexicon-azure 0.124.30 → 0.124.32
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.
- package/dist/actions/appservice.js +7 -0
- package/dist/actions/compute.js +9 -0
- package/dist/actions/container.js +14 -0
- package/dist/actions/identity.js +11 -0
- package/dist/actions/index.js +11 -0
- package/dist/actions/keyvault.js +12 -0
- package/dist/actions/network.js +9 -0
- package/dist/actions/sql.js +9 -0
- package/dist/actions/storage.js +15 -0
- package/dist/api/read-client.js +146 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +83 -0
- package/dist/codegen/generate-cli.js +11 -0
- package/dist/codegen/generate-lexicon.js +77 -0
- package/dist/codegen/generate-typescript.js +163 -0
- package/dist/codegen/generate.js +106 -0
- package/dist/codegen/naming.js +139 -0
- package/dist/codegen/package.js +64 -0
- package/dist/composites/aks-cluster.js +68 -0
- package/dist/composites/app-service.js +62 -0
- package/dist/composites/application-gateway.js +107 -0
- package/dist/composites/catalog.js +856 -0
- package/dist/composites/container-instance.js +36 -0
- package/dist/composites/container-registry.js +62 -0
- package/dist/composites/cosmos-database.js +56 -0
- package/dist/composites/from-arm.js +47 -0
- package/dist/composites/function-app.js +57 -0
- package/dist/composites/index.js +16 -0
- package/dist/composites/keyvault.js +69 -0
- package/dist/composites/landing-zone.js +119 -0
- package/dist/composites/private-endpoint.js +61 -0
- package/dist/composites/redis-cache.js +21 -0
- package/dist/composites/service-bus-pipeline.js +54 -0
- package/dist/composites/sql-database.js +62 -0
- package/dist/composites/storage-account.js +59 -0
- package/dist/composites/vm-linux.js +139 -0
- package/dist/composites/vnet-default.js +102 -0
- package/dist/coverage.js +28 -0
- package/dist/deep-observe.js +356 -0
- package/dist/default-tags.js +50 -0
- package/dist/deploy-scopes.d.ts.map +1 -1
- package/dist/deploy-scopes.js +157 -0
- package/dist/describe-resources.js +187 -0
- package/dist/detect.js +17 -0
- package/dist/export-resources.js +108 -0
- package/dist/generated/index.js +16598 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/governance.js +144 -0
- package/dist/import/generator.js +140 -0
- package/dist/import/live-export.js +39 -0
- package/dist/import/parser.js +192 -0
- package/dist/index.js +44 -0
- package/dist/integrity.json +2 -2
- package/dist/intrinsics.js +179 -0
- package/dist/lint/audit-catalog.js +36 -0
- package/dist/lint/audit-lineage.js +71 -0
- package/dist/lint/post-synth/arm-refs.js +94 -0
- package/dist/lint/post-synth/azr010.js +45 -0
- package/dist/lint/post-synth/azr011.js +43 -0
- package/dist/lint/post-synth/azr012.js +41 -0
- package/dist/lint/post-synth/azr013.js +50 -0
- package/dist/lint/post-synth/azr014.js +49 -0
- package/dist/lint/post-synth/azr015.js +66 -0
- package/dist/lint/post-synth/azr016.js +37 -0
- package/dist/lint/post-synth/azr017.js +37 -0
- package/dist/lint/post-synth/azr018.js +42 -0
- package/dist/lint/post-synth/azr019.js +41 -0
- package/dist/lint/post-synth/azr020.js +37 -0
- package/dist/lint/post-synth/azr021.js +36 -0
- package/dist/lint/post-synth/azr022.js +39 -0
- package/dist/lint/post-synth/azr023.js +39 -0
- package/dist/lint/post-synth/azr024.js +39 -0
- package/dist/lint/post-synth/azr025.js +37 -0
- package/dist/lint/post-synth/azr026.js +38 -0
- package/dist/lint/post-synth/azr027.js +37 -0
- package/dist/lint/post-synth/azr028.js +37 -0
- package/dist/lint/post-synth/azr029.js +38 -0
- package/dist/lint/post-synth/azr030.js +44 -0
- package/dist/lint/post-synth/azr031.js +36 -0
- package/dist/lint/post-synth/azr032.js +53 -0
- package/dist/lint/post-synth/azr033.js +35 -0
- package/dist/lint/post-synth/index.js +50 -0
- package/dist/lint/rules/hardcoded-location.js +82 -0
- package/dist/lint/rules/index.js +6 -0
- package/dist/lint/rules/nsg-wildcard.js +44 -0
- package/dist/lint/rules/storage-https.js +62 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +44 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/az-apply.js +563 -0
- package/dist/op/activities/azure.js +48 -0
- package/dist/op/activities/floci-az.js +37 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +70 -0
- package/dist/ownership.js +13 -0
- package/dist/package-cli.js +21 -0
- package/dist/parameter.js +13 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +472 -0
- package/dist/pseudo.js +40 -0
- package/dist/serializer.js +457 -0
- package/dist/spec/api-versions.js +130 -0
- package/dist/spec/fetch.js +196 -0
- package/dist/spec/parse.js +308 -0
- package/dist/taggable.js +34 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +53 -0
- package/package.json +8 -8
- package/src/codegen/package.ts +2 -2
- package/src/deploy-scopes.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/plugin.ts +3 -2
- package/src/serializer.ts +2 -2
- package/src/taggable.ts +2 -2
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARM template JSON parser — converts ARM templates to intermediate representation.
|
|
3
|
+
*
|
|
4
|
+
* Handles ARM bracket expressions: [resourceId(...)], [reference(...)],
|
|
5
|
+
* [parameters(...)], [variables(...)], [concat(...)], etc.
|
|
6
|
+
*/
|
|
7
|
+
import { BaseValueParser } from "@intentius/chant/import/base-parser";
|
|
8
|
+
/**
|
|
9
|
+
* ARM template parser.
|
|
10
|
+
*/
|
|
11
|
+
export class ArmParser extends BaseValueParser {
|
|
12
|
+
// ARM expresses intrinsics as bracketed string expressions (e.g.
|
|
13
|
+
// "[resourceId(...)]"), not single-key objects, so there are no object-form
|
|
14
|
+
// intrinsics to dispatch during value walking.
|
|
15
|
+
dispatchIntrinsic() {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
parse(content) {
|
|
19
|
+
const template = JSON.parse(content);
|
|
20
|
+
if (!template.resources || !Array.isArray(template.resources)) {
|
|
21
|
+
throw new Error("Invalid ARM template: missing resources array");
|
|
22
|
+
}
|
|
23
|
+
const parameters = [];
|
|
24
|
+
const resources = [];
|
|
25
|
+
// Parse parameters
|
|
26
|
+
if (template.parameters) {
|
|
27
|
+
for (const [name, param] of Object.entries(template.parameters)) {
|
|
28
|
+
const p = param;
|
|
29
|
+
parameters.push({
|
|
30
|
+
name,
|
|
31
|
+
type: mapArmParameterType(p.type),
|
|
32
|
+
description: p.metadata?.description ?? p.description,
|
|
33
|
+
defaultValue: p.defaultValue,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Parse resources
|
|
38
|
+
for (const resource of template.resources) {
|
|
39
|
+
const r = resource;
|
|
40
|
+
const type = r.type;
|
|
41
|
+
const name = r.name;
|
|
42
|
+
// Collect all properties including resource-level fields
|
|
43
|
+
const properties = {};
|
|
44
|
+
// Resource-level fields
|
|
45
|
+
for (const field of ["location", "sku", "kind", "identity", "tags", "zones", "plan"]) {
|
|
46
|
+
if (r[field] !== undefined) {
|
|
47
|
+
properties[field] = this.parseValue(r[field]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Nested properties
|
|
51
|
+
if (r.properties && typeof r.properties === "object") {
|
|
52
|
+
for (const [key, value] of Object.entries(r.properties)) {
|
|
53
|
+
properties[key] = this.parseValue(value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Parse dependsOn
|
|
57
|
+
const dependsOn = r.dependsOn;
|
|
58
|
+
const deps = dependsOn?.map((d) => this.extractResourceName(d)) ?? [];
|
|
59
|
+
resources.push({
|
|
60
|
+
logicalId: name,
|
|
61
|
+
type,
|
|
62
|
+
properties,
|
|
63
|
+
// ResourceIR has no dedicated dependsOn field — preserve ARM dependencies in metadata.
|
|
64
|
+
...(deps.length > 0 && { metadata: { dependsOn: deps } }),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
parameters,
|
|
69
|
+
resources,
|
|
70
|
+
metadata: {
|
|
71
|
+
format: "arm",
|
|
72
|
+
schema: template.$schema,
|
|
73
|
+
contentVersion: template.contentVersion,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse a value, handling ARM bracket expressions.
|
|
79
|
+
*/
|
|
80
|
+
parseValue(value) {
|
|
81
|
+
if (typeof value === "string") {
|
|
82
|
+
return this.parseBracketExpression(value);
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return value.map((v) => this.parseValue(v));
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === "object" && value !== null) {
|
|
88
|
+
const result = {};
|
|
89
|
+
for (const [k, v] of Object.entries(value)) {
|
|
90
|
+
result[k] = this.parseValue(v);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Parse ARM bracket expressions like [resourceId(...)], [reference(...)], etc.
|
|
98
|
+
*/
|
|
99
|
+
parseBracketExpression(value) {
|
|
100
|
+
if (!value.startsWith("[") || !value.endsWith("]"))
|
|
101
|
+
return value;
|
|
102
|
+
// Escaped bracket
|
|
103
|
+
if (value.startsWith("[["))
|
|
104
|
+
return value.slice(1);
|
|
105
|
+
const expr = value.slice(1, -1);
|
|
106
|
+
// resourceId(...)
|
|
107
|
+
const resourceIdMatch = expr.match(/^resourceId\((.+)\)$/);
|
|
108
|
+
if (resourceIdMatch) {
|
|
109
|
+
return { __intrinsic: "ResourceId", args: this.parseArgs(resourceIdMatch[1]) };
|
|
110
|
+
}
|
|
111
|
+
// reference(...)
|
|
112
|
+
const referenceMatch = expr.match(/^reference\((.+)\)$/);
|
|
113
|
+
if (referenceMatch) {
|
|
114
|
+
return { __intrinsic: "Reference", args: this.parseArgs(referenceMatch[1]) };
|
|
115
|
+
}
|
|
116
|
+
// parameters(...)
|
|
117
|
+
const parametersMatch = expr.match(/^parameters\('([^']+)'\)$/);
|
|
118
|
+
if (parametersMatch) {
|
|
119
|
+
return { __intrinsic: "Ref", name: parametersMatch[1] };
|
|
120
|
+
}
|
|
121
|
+
// concat(...)
|
|
122
|
+
const concatMatch = expr.match(/^concat\((.+)\)$/);
|
|
123
|
+
if (concatMatch) {
|
|
124
|
+
return { __intrinsic: "Concat", args: this.parseArgs(concatMatch[1]) };
|
|
125
|
+
}
|
|
126
|
+
// resourceGroup().location, resourceGroup().name, etc.
|
|
127
|
+
const rgMatch = expr.match(/^resourceGroup\(\)\.(\w+)$/);
|
|
128
|
+
if (rgMatch) {
|
|
129
|
+
return { __intrinsic: "ResourceGroup", property: rgMatch[1] };
|
|
130
|
+
}
|
|
131
|
+
// subscription().subscriptionId, etc.
|
|
132
|
+
const subMatch = expr.match(/^subscription\(\)\.(\w+)$/);
|
|
133
|
+
if (subMatch) {
|
|
134
|
+
return { __intrinsic: "Subscription", property: subMatch[1] };
|
|
135
|
+
}
|
|
136
|
+
// uniqueString(...)
|
|
137
|
+
const uniqueMatch = expr.match(/^uniqueString\((.+)\)$/);
|
|
138
|
+
if (uniqueMatch) {
|
|
139
|
+
return { __intrinsic: "UniqueString", args: this.parseArgs(uniqueMatch[1]) };
|
|
140
|
+
}
|
|
141
|
+
// Return raw expression as-is
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Parse comma-separated args from a function call string.
|
|
146
|
+
*/
|
|
147
|
+
parseArgs(argsStr) {
|
|
148
|
+
const args = [];
|
|
149
|
+
let depth = 0;
|
|
150
|
+
let current = "";
|
|
151
|
+
for (const char of argsStr) {
|
|
152
|
+
if (char === "(")
|
|
153
|
+
depth++;
|
|
154
|
+
if (char === ")")
|
|
155
|
+
depth--;
|
|
156
|
+
if (char === "," && depth === 0) {
|
|
157
|
+
args.push(current.trim().replace(/^'|'$/g, ""));
|
|
158
|
+
current = "";
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
current += char;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (current.trim()) {
|
|
165
|
+
args.push(current.trim().replace(/^'|'$/g, ""));
|
|
166
|
+
}
|
|
167
|
+
return args;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Extract a resource name from a dependsOn resourceId expression.
|
|
171
|
+
*/
|
|
172
|
+
extractResourceName(dep) {
|
|
173
|
+
if (dep.startsWith("[")) {
|
|
174
|
+
// Try to extract from resourceId expression
|
|
175
|
+
const match = dep.match(/resourceId\([^,]+,\s*'([^']+)'\)/);
|
|
176
|
+
if (match)
|
|
177
|
+
return match[1];
|
|
178
|
+
}
|
|
179
|
+
return dep;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function mapArmParameterType(type) {
|
|
183
|
+
switch (type?.toLowerCase()) {
|
|
184
|
+
case "string": return "String";
|
|
185
|
+
case "int": return "Number";
|
|
186
|
+
case "bool": return "String";
|
|
187
|
+
case "array": return "CommaDelimitedList";
|
|
188
|
+
case "object": return "String";
|
|
189
|
+
case "securestring": return "String";
|
|
190
|
+
default: return "String";
|
|
191
|
+
}
|
|
192
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @intentius/chant-lexicon-azure — barrel export
|
|
3
|
+
*
|
|
4
|
+
* Azure Resource Manager lexicon for chant.
|
|
5
|
+
*/
|
|
6
|
+
// Typed Op step-builder wrappers (chant #1288 Stage 2) — azApply/azDelete/
|
|
7
|
+
// azGroupEnsure/azGroupDelete/flociAzUp/flociAzDown with authoring-time
|
|
8
|
+
// types derived from this lexicon's own *Args interfaces (see
|
|
9
|
+
// ./op/builders.ts's module doc for why these live here rather than in
|
|
10
|
+
// core). Opt-in: `@intentius/chant/op`'s same-named exports are core's
|
|
11
|
+
// original untyped builders, unchanged.
|
|
12
|
+
export { azApply, azDelete, azGroupEnsure, azGroupDelete, flociAzUp, flociAzDown } from "./op/builders.js";
|
|
13
|
+
// --- Core ---
|
|
14
|
+
export { defaultTags } from "./default-tags.js";
|
|
15
|
+
export { deploymentScope, isDeploymentScope } from "./deploy-scopes.js";
|
|
16
|
+
export { azureSerializer } from "./serializer.js";
|
|
17
|
+
export { azurePlugin } from "./plugin.js";
|
|
18
|
+
export { Parameter } from "./parameter.js";
|
|
19
|
+
// Deep observation (#1086): the `az resource show` reader and the noise
|
|
20
|
+
// rules it shares with core's normalization pass.
|
|
21
|
+
export { observeResourcesDeepAzure, azureDeepNormalizationHooks, AZURE_READ_ONLY_NAMES, AZURE_SERVER_COMPUTED_NAMES, AZURE_SERVICE_DEFAULTS, } from "./deep-observe.js";
|
|
22
|
+
// --- Intrinsics ---
|
|
23
|
+
export { ResourceId, ResourceIdIntrinsic, Reference, ReferenceIntrinsic, Concat, ConcatIntrinsic, ResourceGroup, ResourceGroupIntrinsic, Subscription, SubscriptionIntrinsic, UniqueString, UniqueStringIntrinsic, Format, FormatIntrinsic, If, IfIntrinsic, ListKeys, ListKeysIntrinsic, } from "./intrinsics.js";
|
|
24
|
+
// --- Pseudo-parameters ---
|
|
25
|
+
export { Azure, ResourceGroupName, ResourceGroupLocation, ResourceGroupId, SubscriptionId, TenantId, DeploymentName, } from "./pseudo.js";
|
|
26
|
+
// --- Re-exports from core ---
|
|
27
|
+
export { isChildProject } from "@intentius/chant/child-project";
|
|
28
|
+
export { stackOutput, isStackOutput } from "@intentius/chant/stack-output";
|
|
29
|
+
export { LexiconOutput, output, isLexiconOutput } from "@intentius/chant/lexicon-output";
|
|
30
|
+
// --- Generated resource classes ---
|
|
31
|
+
export * from "./generated/index.js";
|
|
32
|
+
// --- Composites ---
|
|
33
|
+
export { StorageAccountSecure, VnetDefault, VmLinux, AppService, AksCluster, SqlDatabase, KeyVaultSecure, ContainerRegistrySecure, FunctionApp, ServiceBusPipeline, CosmosDatabase, ApplicationGateway, ContainerInstance, RedisCache, PrivateEndpoint, GovernanceFoundation, GovernanceBaseline, LocationRestriction, ActivityLogSink, } from "./composites/index.js";
|
|
34
|
+
// Azure governance authoring: the desired-state config the Azure
|
|
35
|
+
// external governance reconciler applies, and the typed landing-zone authoring layer.
|
|
36
|
+
export { landingZoneConfig, locationRestriction, DENY_CLASSIC_RESOURCES, DENY_UNMANAGED_DISKS, ACTIVITY_LOG_TO_LOG_ANALYTICS_DEFINITION_ID, FOUNDATION_MANAGEMENT_GROUPS, } from "./governance.js";
|
|
37
|
+
// --- RBAC role constants ---
|
|
38
|
+
export { StorageRoles, ComputeRoles, NetworkRoles, KeyVaultRoles, SqlRoles, ContainerRoles, AppServiceRoles, IdentityRoles, } from "./actions/index.js";
|
|
39
|
+
// --- Codegen pipeline ---
|
|
40
|
+
export { generate, writeGeneratedFiles } from "./codegen/generate.js";
|
|
41
|
+
export { packageLexicon } from "./codegen/package.js";
|
|
42
|
+
// --- Spec utilities ---
|
|
43
|
+
export { fetchArmSchemas } from "./spec/fetch.js";
|
|
44
|
+
export { parseArmSchema, armShortName, armServiceName } from "./spec/parse.js";
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "73f94421607348da234c7b2fe2431c917da7c16edc29fa1606a5a41498acf91f",
|
|
5
5
|
"meta.json": "8c7a1ea3cfaf39461d3e758fa2b0cbd70c7800fac736e56df8d14a3e07ef1840",
|
|
6
6
|
"types/index.d.ts": "b20aaa3eeb957a6a5833dcbf5fd99ce1365285eafd4b78dc9383371b1a9d4b61",
|
|
7
7
|
"rules/hardcoded-location.ts": "0b895449c8c94066e81721c46be24249231029fe13b471477456df5c4e45b8bf",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"skills/chant-azure-patterns.md": "1a3bacfac612826b77332d2692a59195638db9f1b5e8ea2eda7579621d25f603",
|
|
38
38
|
"skills/chant-azure-aks.md": "2d4e0098c1a22b54ffadd410564d9df0f3a04cb4eb7c6261f20ae33106e7aca8"
|
|
39
39
|
},
|
|
40
|
-
"composite": "
|
|
40
|
+
"composite": "95455caf05c28b24f7081e35276586087791d81c4b2d88d516a7fd35f5d8a59c"
|
|
41
41
|
}
|