generator/cmd/listresources.ts (87 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import path from 'path'; import { writeFile } from 'fs/promises'; import { resourcesJsonPath, schemasBasePath, schemasBaseUri, } from '../constants'; import { lowerCaseCompare, executeSynchronous, readJsonFile, lowerCaseStartsWith } from '../utils'; const rootSchemaPaths = [ 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json', 'https://schema.management.azure.com/schemas/common/definitions.json', 'https://schema.management.azure.com/schemas/common/autogeneratedResources.json', 'https://schema.management.azure.com/schemas/common/manuallyAddedResources.json', 'https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json', 'https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json', 'https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json', ]; async function readSchema(schemaUri: string) { if (!lowerCaseStartsWith(schemaUri, `${schemasBaseUri}/`)) { throw new Error(`Invalid schema Uri ${schemaUri}`); } const filePath = path.join(schemasBasePath, schemaUri.substring(schemasBaseUri.length + 1)); return await readJsonFile(filePath); } // eslint-disable-next-line @typescript-eslint/no-explicit-any function findAllReferences(input: any) { let refs: string[] = []; for (const key of Object.keys(input)) { if (Array.isArray(input[key])) { for (const value of input[key]) { const foundRefs = findAllReferences(value); refs = refs.concat(foundRefs); } } else if (typeof input[key] === 'object') { const foundRefs = findAllReferences(input[key]); refs = refs.concat(foundRefs); } else if (key === '$ref' && typeof input[key] === 'string') { refs.push(input[key]); } } return refs; } async function getResourceInfo(schemaRef: string) { const schemaUri = schemaRef.split('#')[0]; const relativeRef = schemaRef.split('#')[1].substring(1); let schema = await readSchema(schemaUri); for (const pathElement of relativeRef.split('/')) { schema = schema[pathElement]; } if (!schema?.properties?.type?.enum || !schema?.properties?.apiVersion?.enum) { throw new Error(`Unable to find expected properties for ${schemaRef}`) } const resourceTypes: string[] = schema['properties']['type']['enum']; const apiVersions: string[] = schema['properties']['apiVersion']['enum']; return resourceTypes.map(type => apiVersions.map(apiVersion => ({ apiVersion, type, }))).reduce((a, b) => a.concat(b), []); } async function findAllResourceReferences() { let allRefs: string[] = []; for (const rootSchemaPath of rootSchemaPaths) { const rootSchema = await readSchema(rootSchemaPath); const schemaRefs = findAllReferences(rootSchema) .filter(schema => schema.toLowerCase().startsWith(schemasBaseUri.toLowerCase() + '/')); allRefs = allRefs.concat(schemaRefs); } for (const rootSchemaPath of rootSchemaPaths) { allRefs = allRefs.filter(ref => lowerCaseCompare(ref.split('#')[0], rootSchemaPath) !== 0); } return [...new Set(allRefs)]; } executeSynchronous(async () => { const rootSchemaRefs = await findAllResourceReferences(); const allResources: { [type: string]: string[] } = {}; for (const ref of rootSchemaRefs) { const resources = await getResourceInfo(ref); for (const resource of resources) { // Casing can vary, so do a case-insensitive lookup let resourceKey = Object.keys(allResources).find(key => lowerCaseCompare(key, resource.type) === 0); if (resourceKey === undefined) { resourceKey = resource.type; allResources[resourceKey] = []; } allResources[resourceKey].push(resource.apiVersion); } } for (const resourceType of Object.keys(allResources)) { allResources[resourceType].sort(); } const sortedJsonOutput = JSON.stringify(allResources, Object.keys(allResources).sort(), 2); await writeFile(resourcesJsonPath, sortedJsonOutput, { encoding: 'utf8' }); });