3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
- using System . Diagnostics ;
7
6
using System . Linq ;
8
7
using System . Threading ;
9
8
using Newtonsoft . Json ;
10
9
using Newtonsoft . Json . Linq ;
10
+ using PSRule . Rules . Azure . Data . Template ;
11
11
12
12
namespace PSRule . Rules . Azure
13
13
{
14
- [ DebuggerDisplay ( "Path: {Path}, Line:{LineNumber}, Position:{LinePosition}" ) ]
15
- internal sealed class TemplateTokenAnnotation : IJsonLineInfo
16
- {
17
- private bool _HasLineInfo ;
18
-
19
- public TemplateTokenAnnotation ( )
20
- {
21
-
22
- }
23
-
24
- public TemplateTokenAnnotation ( int lineNumber , int linePosition , string path )
25
- {
26
- SetLineInfo ( lineNumber , linePosition ) ;
27
- Path = path ;
28
- }
29
-
30
- public int LineNumber { get ; private set ; }
31
-
32
- public int LinePosition { get ; private set ; }
33
-
34
- public string Path { get ; private set ; }
35
-
36
- public bool HasLineInfo ( )
37
- {
38
- return _HasLineInfo ;
39
- }
40
-
41
- private void SetLineInfo ( int lineNumber , int linePosition )
42
- {
43
- LineNumber = lineNumber ;
44
- LinePosition = linePosition ;
45
- _HasLineInfo = true ;
46
- }
47
- }
48
-
49
14
internal static class JsonExtensions
50
15
{
51
- private const string PROPERTY_DEPENDSON = "dependsOn" ;
16
+ private const string PROPERTY_DEPENDS_ON = "dependsOn" ;
52
17
private const string PROPERTY_RESOURCES = "resources" ;
53
18
private const string PROPERTY_NAME = "name" ;
54
19
private const string PROPERTY_TYPE = "type" ;
55
20
private const string PROPERTY_FIELD = "field" ;
21
+ private const string PROPERTY_EXISTING = "existing" ;
22
+ private const string PROPERTY_SCOPE = "scope" ;
23
+ private const string PROPERTY_SUBSCRIPTION_ID = "subscriptionId" ;
24
+ private const string PROPERTY_RESOURCE_GROUP = "resourceGroup" ;
25
+
56
26
private const string TARGETINFO_KEY = "_PSRule" ;
57
27
private const string TARGETINFO_SOURCE = "source" ;
58
28
private const string TARGETINFO_FILE = "file" ;
@@ -66,7 +36,9 @@ internal static class JsonExtensions
66
36
private const string TARGETINFO_PATH = "path" ;
67
37
private const string TARGETINFO_MESSAGE = "message" ;
68
38
69
- private static readonly string [ ] JSON_PATH_SEPARATOR = new string [ ] { "." } ;
39
+ private const string TENANT_SCOPE = "/" ;
40
+
41
+ private static readonly string [ ] JSON_PATH_SEPARATOR = [ "." ] ;
70
42
71
43
internal static IJsonLineInfo TryLineInfo ( this JToken token )
72
44
{
@@ -368,7 +340,7 @@ private static string JsonPathJoin(string parent, string child)
368
340
internal static bool TryGetDependencies ( this JObject resource , out string [ ] dependencies )
369
341
{
370
342
dependencies = null ;
371
- if ( ! ( resource . ContainsKey ( PROPERTY_DEPENDSON ) && resource [ PROPERTY_DEPENDSON ] is JArray d && d . Count > 0 ) )
343
+ if ( ! ( resource . ContainsKey ( PROPERTY_DEPENDS_ON ) && resource [ PROPERTY_DEPENDS_ON ] is JArray d && d . Count > 0 ) )
372
344
return false ;
373
345
374
346
dependencies = d . Values < string > ( ) . ToArray ( ) ;
@@ -388,19 +360,19 @@ internal static string GetResourcePath(this JObject resource, int parentLevel =
388
360
389
361
internal static void SetTargetInfo ( this JObject resource , string templateFile , string parameterFile , string path = null )
390
362
{
391
- // Get line infomation
363
+ // Get line information.
392
364
var lineInfo = resource . TryLineInfo ( ) ;
393
365
394
- // Populate target info
366
+ // Populate target info.
395
367
resource . UseProperty ( TARGETINFO_KEY , out JObject targetInfo ) ;
396
368
397
- // Path
369
+ // Path.
398
370
path ??= resource . GetResourcePath ( ) ;
399
371
targetInfo . Add ( TARGETINFO_PATH , path ) ;
400
372
401
373
var sources = new JArray ( ) ;
402
374
403
- // Template file
375
+ // Template file.
404
376
if ( ! string . IsNullOrEmpty ( templateFile ) )
405
377
{
406
378
var source = new JObject
@@ -415,7 +387,8 @@ internal static void SetTargetInfo(this JObject resource, string templateFile, s
415
387
}
416
388
sources . Add ( source ) ;
417
389
}
418
- // Parameter file
390
+
391
+ // Parameter file.
419
392
if ( ! string . IsNullOrEmpty ( parameterFile ) )
420
393
{
421
394
var source = new JObject
@@ -534,5 +507,115 @@ internal static bool IsEmpty(this JToken value)
534
507
value . Type == JTokenType . Null ||
535
508
( value . Type == JTokenType . String && string . IsNullOrEmpty ( value . Value < string > ( ) ) ) ;
536
509
}
510
+
511
+ internal static bool IsExisting ( this JObject o )
512
+ {
513
+ return o != null && o . TryBoolProperty ( PROPERTY_EXISTING , out var existing ) && existing != null && existing . Value ;
514
+ }
515
+
516
+ internal static bool TryResourceType ( this JObject o , out string type )
517
+ {
518
+ type = default ;
519
+ return o != null && o . TryGetProperty ( PROPERTY_TYPE , out type ) ;
520
+ }
521
+
522
+ internal static bool TryResourceName ( this JObject o , out string name )
523
+ {
524
+ name = default ;
525
+ return o != null && o . TryGetProperty ( PROPERTY_NAME , out name ) ;
526
+ }
527
+
528
+ internal static bool TryResourceNameAndType ( this JObject o , out string name , out string type )
529
+ {
530
+ name = default ;
531
+ type = default ;
532
+ return o != null && o . TryResourceName ( out name ) && o . TryResourceType ( out type ) ;
533
+ }
534
+
535
+ /// <summary>
536
+ /// Read the scope from a specified <c>scope</c> property.
537
+ /// </summary>
538
+ /// <param name="resource">The resource object.</param>
539
+ /// <param name="context">A valid context to resolve properties.</param>
540
+ /// <param name="scopeId">The scope if set.</param>
541
+ /// <returns>Returns <c>true</c> if the scope property was set on the resource.</returns>
542
+ internal static bool TryResourceScope ( this JObject resource , ITemplateContext context , out string scopeId )
543
+ {
544
+ scopeId = default ;
545
+ if ( resource == null )
546
+ return false ;
547
+
548
+ return TryExplicitScope ( resource , context , out scopeId )
549
+ || TryExplicitSubscriptionResourceGroupScope ( resource , context , out scopeId )
550
+ || TryParentScope ( resource , context , out scopeId )
551
+ || TryDeploymentScope ( context , out scopeId ) ;
552
+ }
553
+
554
+ private static bool TryExplicitScope ( JObject resource , ITemplateContext context , out string scopeId )
555
+ {
556
+ scopeId = context . ExpandProperty < string > ( resource , PROPERTY_SCOPE ) ;
557
+ if ( string . IsNullOrEmpty ( scopeId ) )
558
+ return false ;
559
+
560
+ // Check for full scope.
561
+ ResourceHelper . ResourceIdComponents ( scopeId , out var tenant , out var managementGroup , out var subscriptionId , out var resourceGroup , out var resourceType , out var resourceName ) ;
562
+ if ( tenant != null || managementGroup != null || subscriptionId != null )
563
+ return true ;
564
+
565
+ scopeId = ResourceHelper . ResourceId ( resourceType , resourceName , scopeId : context . ScopeId ) ;
566
+ return true ;
567
+ }
568
+
569
+ /// <summary>
570
+ /// Get the scope from <c>subscriptionId</c> and <c>resourceGroup</c> properties set on the resource.
571
+ /// </summary>
572
+ private static bool TryExplicitSubscriptionResourceGroupScope ( JObject resource , ITemplateContext context , out string scopeId )
573
+ {
574
+ var subscriptionId = context . ExpandProperty < string > ( resource , PROPERTY_SUBSCRIPTION_ID ) ;
575
+ var resourceGroup = context . ExpandProperty < string > ( resource , PROPERTY_RESOURCE_GROUP ) ;
576
+
577
+ // Fill subscriptionId if resourceGroup is specified.
578
+ if ( ! string . IsNullOrEmpty ( resourceGroup ) && string . IsNullOrEmpty ( subscriptionId ) )
579
+ subscriptionId = context . Subscription . SubscriptionId ;
580
+
581
+ scopeId = ! string . IsNullOrEmpty ( subscriptionId ) ? ResourceHelper . ResourceId ( scopeTenant : null , scopeManagementGroup : null , scopeSubscriptionId : subscriptionId , scopeResourceGroup : resourceGroup , resourceType : null , resourceName : null ) : null ;
582
+ return scopeId != null ;
583
+ }
584
+
585
+ /// <summary>
586
+ /// Read the scope from the name and type properties if this is a sub-resource.
587
+ /// For example: A sub-resource may use name segments such as <c>vnet-1/subnet-1</c>.
588
+ /// </summary>
589
+ /// <param name="resource">The resource object.</param>
590
+ /// <param name="context">A valid context to resolve properties.</param>
591
+ /// <param name="scopeId">The calculated scope.</param>
592
+ /// <returns>Returns <c>true</c> if the scope could be calculated from name segments.</returns>
593
+ private static bool TryParentScope ( JObject resource , ITemplateContext context , out string scopeId )
594
+ {
595
+ scopeId = null ;
596
+ var name = context . ExpandProperty < string > ( resource , PROPERTY_NAME ) ;
597
+ var type = context . ExpandProperty < string > ( resource , PROPERTY_TYPE ) ;
598
+
599
+ if ( string . IsNullOrEmpty ( name ) ||
600
+ string . IsNullOrEmpty ( type ) ||
601
+ ! ResourceHelper . TryResourceIdComponents ( type , name , out var resourceTypeComponents , out var nameComponents ) ||
602
+ resourceTypeComponents . Length == 1 )
603
+ return false ;
604
+
605
+ scopeId = ResourceHelper . GetParentResourceId ( context . Subscription . SubscriptionId , context . ResourceGroup . Name , resourceTypeComponents , nameComponents ) ;
606
+ return true ;
607
+ }
608
+
609
+ /// <summary>
610
+ /// Get the scope of the resource based on the scope of the deployment.
611
+ /// </summary>
612
+ /// <param name="context">A valid context to resolve the deployment scope.</param>
613
+ /// <param name="scopeId">The scope of the deployment.</param>
614
+ /// <returns>Returns <c>true</c> if a deployment scope was found.</returns>
615
+ private static bool TryDeploymentScope ( ITemplateContext context , out string scopeId )
616
+ {
617
+ scopeId = context . Deployment ? . Scope ;
618
+ return scopeId != null ;
619
+ }
537
620
}
538
621
}
0 commit comments