1+ // Copyright (c) Microsoft Corporation.
2+ // Licensed under the MIT License.
3+ import path from 'path' ;
4+ import { writeFile } from 'fs/promises' ;
5+ import { resourcesJsonPath , schemasBasePath , schemasBaseUri , } from '../constants' ;
6+ import { lowerCaseCompare , executeSynchronous , readJsonFile , lowerCaseStartsWith } from '../utils' ;
7+
8+ const rootSchemaPaths = [
9+ 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json' ,
10+ 'https://schema.management.azure.com/schemas/common/definitions.json' ,
11+ 'https://schema.management.azure.com/schemas/common/autogeneratedResources.json' ,
12+ 'https://schema.management.azure.com/schemas/common/manuallyAddedResources.json' ,
13+ 'https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json' ,
14+ 'https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json' ,
15+ 'https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json' ,
16+ ] ;
17+
18+ async function readSchema ( schemaUri : string ) {
19+ if ( ! lowerCaseStartsWith ( schemaUri , `${ schemasBaseUri } /` ) ) {
20+ throw new Error ( `Invalid schema Uri ${ schemaUri } ` ) ;
21+ }
22+
23+ const filePath = path . join ( schemasBasePath , schemaUri . substring ( schemasBaseUri . length + 1 ) ) ;
24+ return await readJsonFile ( filePath ) ;
25+ }
26+
27+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28+ function findAllReferences ( input : any ) {
29+ let refs : string [ ] = [ ] ;
30+
31+ for ( const key of Object . keys ( input ) ) {
32+ if ( Array . isArray ( input [ key ] ) ) {
33+ for ( const value of input [ key ] ) {
34+ const foundRefs = findAllReferences ( value ) ;
35+ refs = refs . concat ( foundRefs ) ;
36+ }
37+ } else if ( typeof input [ key ] === 'object' ) {
38+ const foundRefs = findAllReferences ( input [ key ] ) ;
39+ refs = refs . concat ( foundRefs ) ;
40+ } else if ( key === '$ref' && typeof input [ key ] === 'string' ) {
41+ refs . push ( input [ key ] ) ;
42+ }
43+ }
44+
45+ return refs ;
46+ }
47+
48+ async function getResourceInfo ( schemaRef : string ) {
49+ const schemaUri = schemaRef . split ( '#' ) [ 0 ] ;
50+ const relativeRef = schemaRef . split ( '#' ) [ 1 ] . substring ( 1 ) ;
51+
52+ let schema = await readSchema ( schemaUri ) ;
53+
54+ for ( const pathElement of relativeRef . split ( '/' ) ) {
55+ schema = schema [ pathElement ] ;
56+ }
57+
58+ if ( ! schema ?. properties ?. type ?. enum || ! schema ?. properties ?. apiVersion ?. enum ) {
59+ throw new Error ( `Unable to find expected properties for ${ schemaRef } ` )
60+ }
61+
62+ const resourceTypes : string [ ] = schema [ 'properties' ] [ 'type' ] [ 'enum' ] ;
63+ const apiVersions : string [ ] = schema [ 'properties' ] [ 'apiVersion' ] [ 'enum' ] ;
64+
65+ return resourceTypes . map ( type => apiVersions . map ( apiVersion => ( {
66+ apiVersion,
67+ type,
68+ } ) ) ) . reduce ( ( a , b ) => a . concat ( b ) , [ ] ) ;
69+ }
70+
71+ async function findAllResourceReferences ( ) {
72+ let allRefs : string [ ] = [ ] ;
73+ for ( const rootSchemaPath of rootSchemaPaths ) {
74+ const rootSchema = await readSchema ( rootSchemaPath ) ;
75+ const schemaRefs = findAllReferences ( rootSchema )
76+ . filter ( schema => schema . toLowerCase ( ) . startsWith ( schemasBaseUri . toLowerCase ( ) + '/' ) ) ;
77+
78+ allRefs = allRefs . concat ( schemaRefs ) ;
79+ }
80+
81+ for ( const rootSchemaPath of rootSchemaPaths ) {
82+ allRefs = allRefs . filter ( ref => lowerCaseCompare ( ref . split ( '#' ) [ 0 ] , rootSchemaPath ) !== 0 ) ;
83+ }
84+
85+ return [ ...new Set ( allRefs ) ] ;
86+ }
87+
88+ executeSynchronous ( async ( ) => {
89+ const rootSchemaRefs = await findAllResourceReferences ( ) ;
90+
91+ const allResources : { [ type : string ] : string [ ] } = { } ;
92+ for ( const ref of rootSchemaRefs ) {
93+ const resources = await getResourceInfo ( ref ) ;
94+
95+ for ( const resource of resources ) {
96+ // Casing can vary, so do a case-insensitive lookup
97+ let resourceKey = Object . keys ( allResources ) . find ( key => lowerCaseCompare ( key , resource . type ) === 0 ) ;
98+ if ( resourceKey === undefined ) {
99+ resourceKey = resource . type ;
100+ allResources [ resourceKey ] = [ ] ;
101+ }
102+
103+ allResources [ resourceKey ] . push ( resource . apiVersion ) ;
104+ }
105+ }
106+
107+ for ( const resourceType of Object . keys ( allResources ) ) {
108+ allResources [ resourceType ] . sort ( ) ;
109+ }
110+
111+ const sortedJsonOutput = JSON . stringify ( allResources , Object . keys ( allResources ) . sort ( ) , 2 ) ;
112+ await writeFile ( resourcesJsonPath , sortedJsonOutput , { encoding : 'utf8' } ) ;
113+ } ) ;
0 commit comments