Skip to content

Commit e961ed0

Browse files
authored
Add validation of examples and url for UX metadata files (#20118)
1 parent 27ff424 commit e961ed0

File tree

6 files changed

+236
-40
lines changed

6 files changed

+236
-40
lines changed

src/Compute/Compute/UX/Microsoft.Compute/virtualMachineScaleSets-virtualMachines-runCommands.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
"parameterSets":[
1818
{
1919
"parameters":[
20-
"[-SubscriptionId <String[]>]",
2120
"-ResourceGroupName <String>",
2221
"-InstanceId <String>",
2322
"-RunCommandName <String>",
@@ -31,10 +30,6 @@
3130
{
3231
"description":"The operation to get the VMSS VM run command.",
3332
"parameters":[
34-
{
35-
"name":"-SubscriptionId",
36-
"value":"[path.subscriptionId]"
37-
},
3833
{
3934
"name":"-ResourceGroupName",
4035
"value":"[path.resourceGroupName]"

src/ContainerInstance/UX/Microsoft.ContainerInstance/containerGroups.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@
3535
{
3636
"name":"-ResourceGroupName",
3737
"value":"[path.resourceGroupName]"
38-
},
39-
{
40-
"name":"-VMScaleSetName",
41-
"value":"[path.containerGroupName]"
4238
}
4339
]
4440
}

src/Storage/Storage.Management/UX/Microsoft.Storage/storageAccounts.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"resourceType":"storageAccounts",
33
"apiVersion":"2021-08-01",
44
"learnMore":{
5-
"url":"https://learn.microsoft.com/powershell/module/az.storage/#storage"
5+
"url":"https://learn.microsoft.com/powershell/module/az.storage/#storage"
66
},
77
"commands":[
88
{
@@ -27,7 +27,7 @@
2727
"[-ResourceGroupName] <String>",
2828
"[-Name] <String>",
2929
"[-IncludeBlobRestoreStatus]"
30-
]
30+
]
3131
}
3232
]
3333
},
@@ -68,7 +68,7 @@
6868
"examples":[
6969
{
7070
"description":"Invoke failover of a Storage account.",
71-
"parameters":[
71+
"parameters":[
7272
{
7373
"name":"-ResourceGroupName",
7474
"value":"[path.resourceGroupName]"
@@ -103,7 +103,7 @@
103103
"examples":[
104104
{
105105
"description":"Aborts an ongoing HierarchicalNamespace upgrade task on a storage account.",
106-
"parameters":[
106+
"parameters":[
107107
{
108108
"name":"-ResourceGroupName",
109109
"value":"[path.resourceGroupName]"
@@ -123,13 +123,13 @@
123123
"confirmation":true,
124124
"help":{
125125
"learnMore":{
126-
"url":"https://learn.microsoft.com/powershell/module/az.storage/stop-azstorageaccounthierarchicalnamespaceupgrade"
126+
"url":"https://learn.microsoft.com/powershell/module/az.storage/revoke-azstorageaccountuserdelegationkeys"
127127
},
128128
"parameterSets":[
129129
{
130130
"parameters":[
131131
"[-ResourceGroupName] <String>",
132-
"[-Name] <String>",
132+
"[-StorageAccountName] <String>",
133133
"[-PassThru]"
134134
]
135135
}
@@ -138,13 +138,13 @@
138138
"examples":[
139139
{
140140
"description":"Revoke all User Delegation keys of a Storage account.",
141-
"parameters":[
141+
"parameters":[
142142
{
143143
"name":"-ResourceGroupName",
144144
"value":"[path.resourceGroupName]"
145145
},
146146
{
147-
"name":"-Name",
147+
"name":"-StorageAccountName",
148148
"value":"[path.accountName]"
149149
}
150150
]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System.Collections.Generic;
16+
17+
namespace StaticAnalysis.UXMetadataAnalyzer
18+
{
19+
internal class UXMetadata
20+
{
21+
public string ResourceType { get; set; }
22+
public string ApiVersion { get; set; }
23+
public UXMetadataLearnMore LearnMore { get; set; }
24+
public List<UXMetadataCommand> Commands { get; set; }
25+
}
26+
27+
internal class UXMetadataLearnMore
28+
{
29+
public string Url { get; set; }
30+
}
31+
32+
internal class UXMetadataCommand
33+
{
34+
public string Name { get; set; }
35+
public string Description { get; set; }
36+
public string Path { get; set; }
37+
public bool Confirmation { get; set; }
38+
public UXMetadataCommandHelp Help { get; set; }
39+
public List<UXMetadataCommandExample> Examples { get; set; }
40+
}
41+
42+
internal class UXMetadataCommandExample
43+
{
44+
public string Description { get; set; }
45+
public List<UXMetadataCommandExamplePatameter> Parameters { get; set; }
46+
}
47+
48+
internal class UXMetadataCommandExamplePatameter
49+
{
50+
public string Name { get; set; }
51+
public string Value { get; set; }
52+
}
53+
54+
internal class UXMetadataCommandHelp
55+
{
56+
public UXMetadataLearnMore LearnMore { get; set; }
57+
public List<UXMetadataCommandHelpParameterSet> ParameterSets { get; set; }
58+
}
59+
60+
internal class UXMetadataCommandHelpParameterSet
61+
{
62+
public List<string> Parameters { get; set; }
63+
}
64+
}

tools/StaticAnalysis/UXMetadataAnalyzer/UXMetadataAnalyzer.cs

Lines changed: 149 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15-
//#define SERIALIZE
16-
1715
using Newtonsoft.Json;
1816

1917
using NJsonSchema;
@@ -23,14 +21,18 @@
2321
using System.Collections.Generic;
2422
using System.IO;
2523
using System.Linq;
26-
using System.Management.Automation;
2724
using System.Reflection;
25+
using System.Text.RegularExpressions;
26+
2827
using Tools.Common.Issues;
2928
using Tools.Common.Loaders;
3029
using Tools.Common.Loggers;
3130
using Tools.Common.Models;
3231
using Tools.Common.Utilities;
3332

33+
using ParameterMetadata = Tools.Common.Models.ParameterMetadata;
34+
using ParameterSetMetadata = Tools.Common.Models.ParameterSetMetadata;
35+
3436
namespace StaticAnalysis.UXMetadataAnalyzer
3537
{
3638
public class UXMetadataAnalyzer : IStaticAnalyzer
@@ -75,7 +77,7 @@ public void Analyze(
7577
Func<string, bool> cmdletFilter,
7678
IEnumerable<string> modulesToAnalyze)
7779
{
78-
var processedHelpFiles = new List<string>();
80+
var savedDirectory = Directory.GetCurrentDirectory();
7981
var issueLogger = Logger.CreateLogger<UXMetadataIssue>("UXMetadataIssues.csv");
8082

8183
if (directoryFilter != null)
@@ -102,6 +104,10 @@ public void Analyze(
102104
}
103105
string moduleName = Path.GetFileName(directory);
104106

107+
Directory.SetCurrentDirectory(directory);
108+
109+
var moduleMetadata = MetadataLoader.GetModuleMetadata(moduleName);
110+
105111
string UXFolder = Path.Combine(directory, "UX");
106112
if (!Directory.Exists(UXFolder))
107113
{
@@ -111,24 +117,154 @@ public void Analyze(
111117
var UXMetadataPathList = Directory.EnumerateFiles(UXFolder, "*.json", SearchOption.AllDirectories);
112118
foreach (var UXMetadataPath in UXMetadataPathList)
113119
{
114-
ValidateUXMetadata(moduleName, UXMetadataPath, issueLogger);
120+
ValidateUXMetadata(moduleName, UXMetadataPath, moduleMetadata, issueLogger);
115121
}
122+
Directory.SetCurrentDirectory(savedDirectory);
116123
}
117124
}
118125
}
119126

120-
private void ValidateUXMetadata(string moduleName, string UXMatadataPath, ReportLogger<UXMetadataIssue> issueLogger)
127+
private void ValidateSchema(string moduleName, string resourceType, string subResourceType, string UXMetadataContent, ReportLogger<UXMetadataIssue> issueLogger)
121128
{
122-
string data = File.ReadAllText(UXMatadataPath);
123-
var result = schemaValidator.Validate(data, schema);
124-
string resourceType = Path.GetFileName(Path.GetDirectoryName(UXMatadataPath));
129+
var result = schemaValidator.Validate(UXMetadataContent, schema);
125130
if (result != null && result.Count != 0)
126131
{
127132
foreach (ValidationError error in result)
128133
{
129-
issueLogger.LogUXMetadataIssue(moduleName, resourceType, UXMatadataPath, 1, error.ToString().Replace("\n", "\\n"));
134+
issueLogger.LogUXMetadataIssue(moduleName, resourceType, subResourceType, null, 1, error.ToString().Replace("\n", "\\n"));
135+
}
136+
}
137+
}
138+
139+
private void ValidateMetadata(string moduleName, string resourceType, string subResourceType, string UXMetadataContent, ModuleMetadata moduleMetadata, ReportLogger<UXMetadataIssue> issueLogger)
140+
{
141+
UXMetadata UXMetadata = JsonConvert.DeserializeObject<UXMetadata>(UXMetadataContent);
142+
143+
foreach (UXMetadataCommand command in UXMetadata.Commands)
144+
{
145+
string expectLearnUrl = string.Format("https://learn.microsoft.com/powershell/module/{0}/{1}", moduleName, command.Name).ToLower();
146+
147+
if (!expectLearnUrl.Equals(command.Help.LearnMore.Url, StringComparison.OrdinalIgnoreCase))
148+
{
149+
string description = string.Format("Doc url is expect: {0} but get: {1}", expectLearnUrl, command.Help.LearnMore.Url);
150+
issueLogger.LogUXMetadataIssue(moduleName, resourceType, subResourceType, command.Name, 1, description);
151+
}
152+
if (command.Path.IndexOf(resourceType, StringComparison.CurrentCultureIgnoreCase) == -1)
153+
{
154+
string description = string.Format("The path {0} doesn't contains the right resource tpye: {1}", command.Path, resourceType);
155+
issueLogger.LogUXMetadataIssue(moduleName, resourceType, subResourceType, command.Name, 2, description);
156+
}
157+
158+
CmdletMetadata cmdletMetadata = moduleMetadata.Cmdlets.Find(x => x.Name == command.Name);
159+
if (cmdletMetadata == null)
160+
{
161+
string description = string.Format("Cmdlet {0} is not contained in {1}.", command.Name, moduleName);
162+
issueLogger.LogUXMetadataIssue(moduleName, resourceType, subResourceType, command.Name, 1, description);
163+
}
164+
165+
foreach (UXMetadataCommandExample example in command.Examples)
166+
{
167+
ValidateExample(moduleName, resourceType, subResourceType, command.Name, cmdletMetadata, example, issueLogger);
168+
}
169+
}
170+
}
171+
172+
private void ValidateExample(string moduleName, string resourceType, string subResourceType, string commandName, CmdletMetadata cmdletMetadata, UXMetadataCommandExample example, ReportLogger<UXMetadataIssue> issueLogger)
173+
{
174+
List<string> parameterListConvertedFromAlias = example.Parameters.Select(x =>
175+
{
176+
string parameterNameInExample = x.Name.Trim('-');
177+
foreach (ParameterMetadata parameterMetadata in cmdletMetadata.Parameters)
178+
{
179+
if (parameterMetadata.Name.Equals(parameterNameInExample, StringComparison.CurrentCultureIgnoreCase))
180+
{
181+
return parameterMetadata.Name;
182+
}
183+
foreach (string alias in parameterMetadata.AliasList)
184+
{
185+
if (alias.Equals(parameterNameInExample, StringComparison.CurrentCultureIgnoreCase))
186+
{
187+
return parameterMetadata.Name;
188+
}
189+
}
190+
}
191+
string description = string.Format("Cannot find the defination of parameter {0} in example", parameterNameInExample);
192+
issueLogger.LogUXMetadataIssue(moduleName, resourceType, subResourceType, commandName, 1, description);
193+
return null;
194+
}).ToList();
195+
196+
HashSet<string> parametersInExample = new HashSet<string>(parameterListConvertedFromAlias.Where(x => x != null));
197+
foreach (string parameter in parametersInExample)
198+
{
199+
if (parameterListConvertedFromAlias.Count(x => parameter.Equals(x)) != 1)
200+
{
201+
string description = string.Format("Multiply reference of parameter {0} in example", parameter);
202+
issueLogger.LogUXMetadataIssue(moduleName, resourceType, subResourceType, commandName, 1, description);
130203
}
131204
}
205+
if (parameterListConvertedFromAlias.Contains(null))
206+
{
207+
return;
208+
}
209+
210+
bool findMatchedParameterSet = false;
211+
foreach (ParameterSetMetadata parameterSetMetadata in cmdletMetadata.ParameterSets)
212+
{
213+
if (IsExampleMatchParameterSet(parametersInExample, parameterSetMetadata))
214+
{
215+
findMatchedParameterSet = true;
216+
}
217+
}
218+
219+
if (!findMatchedParameterSet)
220+
{
221+
string description = string.Format("Cannot find a matched parameter set for example of {0}", commandName);
222+
issueLogger.LogUXMetadataIssue(moduleName, resourceType, subResourceType, commandName, 1, description);
223+
}
224+
}
225+
226+
private bool IsExampleMatchParameterSet(HashSet<string> parametersInExample, ParameterSetMetadata parameterSetMetadata)
227+
{
228+
List<Parameter> mandatoryParameters = parameterSetMetadata.Parameters.Where(x => x.Mandatory).ToList();
229+
foreach (Parameter parameter in mandatoryParameters)
230+
{
231+
if (!parametersInExample.Contains(parameter.ParameterMetadata.Name))
232+
{
233+
return false;
234+
}
235+
}
236+
237+
foreach (string parameterName in parametersInExample)
238+
{
239+
if (!IsParameterContainedInParameterSet(parameterName, parameterSetMetadata))
240+
{
241+
return false;
242+
}
243+
}
244+
245+
return true;
246+
}
247+
248+
private bool IsParameterContainedInParameterSet(string paramenterName, ParameterSetMetadata parameterSetMetadata)
249+
{
250+
foreach (Parameter parameterInfo in parameterSetMetadata.Parameters)
251+
{
252+
if (parameterInfo.ParameterMetadata.Name.Equals(paramenterName, StringComparison.CurrentCultureIgnoreCase))
253+
{
254+
return true;
255+
}
256+
}
257+
258+
return false;
259+
}
260+
261+
private void ValidateUXMetadata(string moduleName, string UXMetadataPath, ModuleMetadata moduleMetadata, ReportLogger<UXMetadataIssue> issueLogger)
262+
{
263+
string UXMetadataContent = File.ReadAllText(UXMetadataPath);
264+
string resourceType = Path.GetFileName(Path.GetDirectoryName(UXMetadataPath));
265+
string subResourceType = Path.GetFileName(UXMetadataPath).Replace(".json", "");
266+
ValidateSchema(moduleName, resourceType, subResourceType, UXMetadataContent, issueLogger);
267+
ValidateMetadata(moduleName, resourceType, subResourceType, UXMetadataContent, moduleMetadata, issueLogger);
132268
}
133269

134270

@@ -150,14 +286,15 @@ public AnalysisReport GetAnalysisReport()
150286
public static class LogExtensions
151287
{
152288
public static void LogUXMetadataIssue(
153-
this ReportLogger<UXMetadataIssue> issueLogger, string module, string resourceType, string filePath,
289+
this ReportLogger<UXMetadataIssue> issueLogger, string module, string resourceType, string subResourceType, string command,
154290
int severity, string description)
155291
{
156292
issueLogger.LogRecord(new UXMetadataIssue
157293
{
158294
Module = module,
159295
ResourceType = resourceType,
160-
FilePath = filePath,
296+
SubResourceType = subResourceType,
297+
Command = command,
161298
Description = description,
162299
Severity = severity,
163300
});

0 commit comments

Comments
 (0)