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