Skip to content

Commit 08bbc1d

Browse files
Copilotalex-slynko
andauthored
Add managed identity policy KQL generation for Azure Data Explorer databases
Agent-Logs-Url: https://github.com/github/KustoSchemaTools/sessions/10ba8c30-dcb5-4cce-8f5c-7f54024a1299 Co-authored-by: alex-slynko <4385389+alex-slynko@users.noreply.github.com>
1 parent 7fee3c0 commit 08bbc1d

File tree

4 files changed

+180
-0
lines changed

4 files changed

+180
-0
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
using KustoSchemaTools.Model;
2+
using KustoSchemaTools.Changes;
3+
using Microsoft.Extensions.Logging;
4+
using Moq;
5+
6+
namespace KustoSchemaTools.Tests.ManagedIdentity
7+
{
8+
public class ManagedIdentityPolicyTests
9+
{
10+
[Fact]
11+
public void CreateScript_SingleUsage_GeneratesCorrectKql()
12+
{
13+
// Arrange
14+
var policy = new ManagedIdentityPolicy
15+
{
16+
ObjectId = "12345678-1234-1234-1234-123456789abc",
17+
AllowedUsages = new List<string> { "NativeIngestion" }
18+
};
19+
20+
// Act
21+
var script = policy.CreateScript("MyDatabase");
22+
23+
// Assert
24+
Assert.Equal("ManagedIdentityPolicy", script.Kind);
25+
Assert.Equal(80, script.Script.Order);
26+
Assert.Contains(".alter-merge database MyDatabase policy managed_identity", script.Script.Text);
27+
Assert.Contains("\"ObjectId\": \"12345678-1234-1234-1234-123456789abc\"", script.Script.Text);
28+
Assert.Contains("\"AllowedUsages\": \"NativeIngestion\"", script.Script.Text);
29+
}
30+
31+
[Fact]
32+
public void CreateScript_MultipleUsages_JoinsWithComma()
33+
{
34+
// Arrange
35+
var policy = new ManagedIdentityPolicy
36+
{
37+
ObjectId = "12345678-1234-1234-1234-123456789abc",
38+
AllowedUsages = new List<string> { "AutomatedFlows", "ExternalTable", "NativeIngestion" }
39+
};
40+
41+
// Act
42+
var script = policy.CreateScript("MyDatabase");
43+
44+
// Assert
45+
Assert.Contains("\"AllowedUsages\": \"AutomatedFlows, ExternalTable, NativeIngestion\"", script.Script.Text);
46+
}
47+
48+
[Fact]
49+
public void CreateScript_DatabaseNameUsedInKql()
50+
{
51+
// Arrange
52+
var policy = new ManagedIdentityPolicy
53+
{
54+
ObjectId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
55+
AllowedUsages = new List<string> { "ExternalTable" }
56+
};
57+
58+
// Act
59+
var script = policy.CreateScript("TargetDatabase");
60+
61+
// Assert
62+
Assert.StartsWith(".alter-merge database TargetDatabase policy managed_identity", script.Script.Text);
63+
}
64+
65+
[Fact]
66+
public void CreateScript_WrapsJsonInBackticks()
67+
{
68+
// Arrange
69+
var policy = new ManagedIdentityPolicy
70+
{
71+
ObjectId = "12345678-1234-1234-1234-123456789abc",
72+
AllowedUsages = new List<string> { "NativeIngestion" }
73+
};
74+
75+
// Act
76+
var script = policy.CreateScript("MyDatabase");
77+
78+
// Assert
79+
Assert.Contains("```", script.Script.Text);
80+
Assert.EndsWith("```", script.Script.Text);
81+
}
82+
83+
[Fact]
84+
public void DatabaseChanges_WithManagedIdentityPolicies_GeneratesScript()
85+
{
86+
// Arrange
87+
var loggerMock = new Mock<ILogger>();
88+
var oldState = new Database { Name = "TestDb" };
89+
var newState = new Database
90+
{
91+
Name = "TestDb",
92+
ManagedIdentityPolicies = new List<ManagedIdentityPolicy>
93+
{
94+
new ManagedIdentityPolicy
95+
{
96+
ObjectId = "12345678-1234-1234-1234-123456789abc",
97+
AllowedUsages = new List<string> { "NativeIngestion" }
98+
}
99+
}
100+
};
101+
102+
// Act
103+
var changes = DatabaseChanges.GenerateChanges(oldState, newState, "TestDb", loggerMock.Object);
104+
105+
// Assert
106+
Assert.NotEmpty(changes);
107+
var scripts = changes.SelectMany(c => c.Scripts).ToList();
108+
Assert.NotEmpty(scripts);
109+
var managedIdentityScript = scripts.FirstOrDefault(s => s.Kind == "ManagedIdentityPolicy");
110+
Assert.NotNull(managedIdentityScript);
111+
Assert.Contains(".alter-merge database TestDb policy managed_identity", managedIdentityScript.Script.Text);
112+
Assert.Contains("12345678-1234-1234-1234-123456789abc", managedIdentityScript.Script.Text);
113+
}
114+
115+
[Fact]
116+
public void DatabaseChanges_WithUnchangedManagedIdentityPolicies_GeneratesNoChanges()
117+
{
118+
// Arrange
119+
var loggerMock = new Mock<ILogger>();
120+
var policy = new ManagedIdentityPolicy
121+
{
122+
ObjectId = "12345678-1234-1234-1234-123456789abc",
123+
AllowedUsages = new List<string> { "NativeIngestion" }
124+
};
125+
var oldState = new Database
126+
{
127+
Name = "TestDb",
128+
ManagedIdentityPolicies = new List<ManagedIdentityPolicy> { policy }
129+
};
130+
var newState = new Database
131+
{
132+
Name = "TestDb",
133+
ManagedIdentityPolicies = new List<ManagedIdentityPolicy>
134+
{
135+
new ManagedIdentityPolicy
136+
{
137+
ObjectId = "12345678-1234-1234-1234-123456789abc",
138+
AllowedUsages = new List<string> { "NativeIngestion" }
139+
}
140+
}
141+
};
142+
143+
// Act
144+
var changes = DatabaseChanges.GenerateChanges(oldState, newState, "TestDb", loggerMock.Object);
145+
146+
// Assert - no database-level changes since policies are identical
147+
var databaseScriptChanges = changes
148+
.SelectMany(c => c.Scripts)
149+
.Where(s => s.Kind == "ManagedIdentityPolicy")
150+
.ToList();
151+
Assert.Empty(databaseScriptChanges);
152+
}
153+
}
154+
}

KustoSchemaTools/Changes/DatabaseChanges.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ public static List<IChange> GenerateChanges(Database oldState, Database newState
2222
otherFromScripts.AddRange(oldState.Scripts.Select(itm => new DatabaseScriptContainer(itm, "DatabaseScript")));
2323
if (oldState.DefaultRetentionAndCache != null)
2424
otherFromScripts.AddRange(oldState.DefaultRetentionAndCache.CreateScripts(name, "database"));
25+
if (oldState.ManagedIdentityPolicies != null)
26+
otherFromScripts.AddRange(oldState.ManagedIdentityPolicies.Select(p => p.CreateScript(name)));
2527
}
2628

2729
var otherToScripts = new List<DatabaseScriptContainer>();
2830
if (newState.Scripts != null)
2931
otherToScripts.AddRange(newState.Scripts.Select(itm => new DatabaseScriptContainer(itm, "DatabaseScript")));
3032
if (newState.DefaultRetentionAndCache != null)
3133
otherToScripts.AddRange(newState.DefaultRetentionAndCache.CreateScripts(name, "database"));
34+
if (newState.ManagedIdentityPolicies != null)
35+
otherToScripts.AddRange(newState.ManagedIdentityPolicies.Select(p => p.CreateScript(name)));
3236

3337
if (otherToScripts.Count > 0)
3438
{

KustoSchemaTools/Model/Database.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public class Database
3737

3838
public Dictionary<string, FollowerDatabase> Followers { get; set; } = new Dictionary<string, FollowerDatabase>();
3939

40+
public List<ManagedIdentityPolicy> ManagedIdentityPolicies { get; set; } = new List<ManagedIdentityPolicy>();
41+
4042
public string EscapedName => Name.BracketIfIdentifier();
4143
}
4244
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using KustoSchemaTools.Changes;
2+
using Newtonsoft.Json;
3+
using KustoSchemaTools.Helpers;
4+
using KustoSchemaTools.Parser;
5+
6+
namespace KustoSchemaTools.Model
7+
{
8+
public class ManagedIdentityPolicy
9+
{
10+
public string ObjectId { get; set; }
11+
public List<string> AllowedUsages { get; set; } = new List<string>();
12+
13+
public DatabaseScriptContainer CreateScript(string databaseName)
14+
{
15+
var policyObjects = new[] { new { ObjectId = ObjectId, AllowedUsages = string.Join(", ", AllowedUsages) } };
16+
var json = JsonConvert.SerializeObject(policyObjects, Serialization.JsonPascalCase);
17+
return new DatabaseScriptContainer("ManagedIdentityPolicy", 80, $".alter-merge database {databaseName.BracketIfIdentifier()} policy managed_identity ```{json}```");
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)