Skip to content

Commit 2fb5c70

Browse files
committed
Adding stack deployment
1 parent f2c71ae commit 2fb5c70

File tree

9 files changed

+175
-108
lines changed

9 files changed

+175
-108
lines changed

.kiro/settings/mcp.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@
3535
"aws___search_documentation",
3636
"aws___read_documentation"
3737
]
38+
},
39+
"codeloom-mcp": {
40+
"disabled": false,
41+
"command": "code-loom-mcp",
42+
"args": [],
43+
"env": {},
44+
"transportType": "stdio",
45+
"autoApprove": [
46+
"loomer",
47+
"search_aws_docs",
48+
"read_aws_docs",
49+
"query_knowledge_bases",
50+
"list_knowledge_bases"
51+
]
3852
}
3953
}
4054
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<OutputType>Exe</OutputType>
4+
<OutputType>Library</OutputType>
55
<TargetFramework>net8.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="AWSSDK.CloudFormation" Version="3.7.407.7" />
12-
<PackageReference Include="AWSSDK.CloudWatchLogs" Version="3.7.403.14" />
13-
<PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.301" />
14-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
15-
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
11+
<PackageReference Include="AWSSDK.CloudFormation" Version="3.7.*" />
12+
<PackageReference Include="AWSSDK.CloudWatchLogs" Version="3.7.*" />
13+
<PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.*" />
14+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.*" />
15+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.*" />
1616
</ItemGroup>
1717

1818
</Project>

dotnetv4/CloudWatchLogs/LargeQuery/Actions/CloudWatchLogsWrapper.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
// snippet-start:[CloudWatchLogs.dotnetv3.CloudWatchLogsWrapper]
4+
// snippet-start:[CloudWatchLogs.dotnetv4.CloudWatchLogsWrapper]
55
using Amazon.CloudWatchLogs;
66
using Amazon.CloudWatchLogs.Model;
77
using Microsoft.Extensions.Logging;
@@ -27,7 +27,7 @@ public CloudWatchLogsWrapper(IAmazonCloudWatchLogs amazonCloudWatchLogs, ILogger
2727
_logger = logger;
2828
}
2929

30-
// snippet-start:[CloudWatchLogs.dotnetv3.StartQuery]
30+
// snippet-start:[CloudWatchLogs.dotnetv4.StartQuery]
3131
/// <summary>
3232
/// Starts a CloudWatch Logs Insights query.
3333
/// </summary>
@@ -74,9 +74,9 @@ public CloudWatchLogsWrapper(IAmazonCloudWatchLogs amazonCloudWatchLogs, ILogger
7474
return null;
7575
}
7676
}
77-
// snippet-end:[CloudWatchLogs.dotnetv3.StartQuery]
77+
// snippet-end:[CloudWatchLogs.dotnetv4.StartQuery]
7878

79-
// snippet-start:[CloudWatchLogs.dotnetv3.GetQueryResults]
79+
// snippet-start:[CloudWatchLogs.dotnetv4.GetQueryResults]
8080
/// <summary>
8181
/// Gets the results of a CloudWatch Logs Insights query.
8282
/// </summary>
@@ -105,9 +105,9 @@ public CloudWatchLogsWrapper(IAmazonCloudWatchLogs amazonCloudWatchLogs, ILogger
105105
return null;
106106
}
107107
}
108-
// snippet-end:[CloudWatchLogs.dotnetv3.GetQueryResults]
108+
// snippet-end:[CloudWatchLogs.dotnetv4.GetQueryResults]
109109

110-
// snippet-start:[CloudWatchLogs.dotnetv3.PutLogEvents]
110+
// snippet-start:[CloudWatchLogs.dotnetv4.PutLogEvents]
111111
/// <summary>
112112
/// Puts log events to a CloudWatch Logs log stream.
113113
/// </summary>
@@ -143,6 +143,6 @@ public async Task<bool> PutLogEventsAsync(
143143
return false;
144144
}
145145
}
146-
// snippet-end:[CloudWatchLogs.dotnetv3.PutLogEvents]
146+
// snippet-end:[CloudWatchLogs.dotnetv4.PutLogEvents]
147147
}
148-
// snippet-end:[CloudWatchLogs.dotnetv3.CloudWatchLogsWrapper]
148+
// snippet-end:[CloudWatchLogs.dotnetv4.CloudWatchLogsWrapper]

dotnetv4/CloudWatchLogs/LargeQuery/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ LargeQuery/
5454

5555
- .NET 8.0 or later
5656
- AWS credentials configured
57-
- Python 3.x (for log generation)
5857
- Permissions for CloudWatch Logs and CloudFormation
5958

6059
## Related Resources

dotnetv4/CloudWatchLogs/LargeQuery/Scenarios/CloudWatchLogsScenario.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="AWSSDK.CloudFormation" Version="3.7.407.7" />
12-
<PackageReference Include="AWSSDK.CloudWatchLogs" Version="3.7.403.14" />
13-
<PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.301" />
14-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
15-
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
11+
<PackageReference Include="AWSSDK.CloudFormation" Version="3.7.*" />
12+
<PackageReference Include="AWSSDK.CloudWatchLogs" Version="3.7.*" />
13+
<PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.*" />
14+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.*" />
15+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.*" />
1616
</ItemGroup>
1717

1818
<ItemGroup>

dotnetv4/CloudWatchLogs/LargeQuery/Scenarios/LargeQueryWorkflow.cs

Lines changed: 111 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
// snippet-start:[CloudWatchLogs.dotnetv3.LargeQueryWorkflow]
4+
// snippet-start:[CloudWatchLogs.dotnetv4.LargeQueryWorkflow]
55
using System.Diagnostics;
66
using System.Text.RegularExpressions;
77
using Amazon.CloudFormation;
@@ -24,7 +24,7 @@ public class LargeQueryWorkflow
2424
1. Prepare the Application:
2525
- Prompt the user to deploy CloudFormation stack and generate sample logs.
2626
- Deploy the CloudFormation template for resource creation.
27-
- Generate 50,000 sample log entries using a Python script.
27+
- Generate 50,000 sample log entries using CloudWatch Logs API.
2828
- Wait 5 minutes for logs to be fully ingested.
2929
3030
2. Execute Large Query:
@@ -48,8 +48,7 @@ public class LargeQueryWorkflow
4848

4949
public static bool _interactive = true;
5050
private static string _stackName = "CloudWatchLargeQueryStack";
51-
private static string _stackResourcePath = "../../../../../../scenarios/features/cloudwatch_logs_large_query/resources/stack.yaml";
52-
private static string _pythonScriptPath = "../../../../../../scenarios/features/cloudwatch_logs_large_query/resources/create_logs.py";
51+
private static string _stackResourcePath = "../../../../../../../scenarios/features/cloudwatch_logs_large_query/resources/stack.yaml";
5352

5453
public static async Task Main(string[] args)
5554
{
@@ -147,8 +146,8 @@ public static async Task<bool> PrepareApplication()
147146
}
148147
else
149148
{
150-
_logGroupName = PromptUserForInput("Enter the log group name: ", _logGroupName);
151-
_logStreamName = PromptUserForInput("Enter the log stream name: ", _logStreamName);
149+
_logGroupName = PromptUserForInput("Enter the log group name ", _logGroupName);
150+
_logStreamName = PromptUserForInput("Enter the log stream name ", _logStreamName);
152151

153152
var startDateMs = PromptUserForLong("Enter the query start date (milliseconds since epoch): ");
154153
var endDateMs = PromptUserForLong("Enter the query end date (milliseconds since epoch): ");
@@ -267,65 +266,65 @@ private static async Task<bool> WaitForStackCompletion(string stackId)
267266
}
268267

269268
/// <summary>
270-
/// Generates sample logs using a Python script.
269+
/// Generates sample logs directly using CloudWatch Logs API.
270+
/// Creates 50,000 log entries spanning 5 minutes.
271271
/// </summary>
272272
/// <returns>True if logs were generated successfully.</returns>
273273
private static async Task<bool> GenerateSampleLogs()
274274
{
275+
const int totalEntries = 50000;
276+
const int entriesPerBatch = 10000;
277+
const int fiveMinutesMs = 5 * 60 * 1000;
278+
275279
try
276280
{
277-
if (!File.Exists(_pythonScriptPath))
278-
{
279-
_logger.LogError($"Python script not found at: {_pythonScriptPath}");
280-
Console.WriteLine("Please run the script manually from:");
281-
Console.WriteLine($" {_pythonScriptPath}");
282-
return false;
283-
}
281+
// Calculate timestamps
282+
var startTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
283+
var timestampIncrement = fiveMinutesMs / totalEntries;
284284

285-
var processStartInfo = new ProcessStartInfo
286-
{
287-
FileName = "python",
288-
Arguments = _pythonScriptPath,
289-
RedirectStandardOutput = true,
290-
RedirectStandardError = true,
291-
UseShellExecute = false,
292-
CreateNoWindow = true
293-
};
285+
Console.WriteLine($"Generating {totalEntries} log entries...");
294286

295-
using var process = Process.Start(processStartInfo);
296-
if (process == null)
287+
var entryCount = 0;
288+
var currentTimestamp = startTimeMs;
289+
var numBatches = totalEntries / entriesPerBatch;
290+
291+
// Generate and upload logs in batches
292+
for (int batchNum = 0; batchNum < numBatches; batchNum++)
297293
{
298-
_logger.LogError("Failed to start Python process.");
299-
return false;
300-
}
294+
var logEvents = new List<InputLogEvent>();
301295

302-
var output = await process.StandardOutput.ReadToEndAsync();
303-
var error = await process.StandardError.ReadToEndAsync();
304-
await process.WaitForExitAsync();
296+
for (int i = 0; i < entriesPerBatch; i++)
297+
{
298+
logEvents.Add(new InputLogEvent
299+
{
300+
Timestamp = DateTimeOffset.FromUnixTimeMilliseconds(currentTimestamp).UtcDateTime,
301+
Message = $"Entry {entryCount}"
302+
});
305303

306-
if (process.ExitCode != 0)
307-
{
308-
_logger.LogError($"Python script failed: {error}");
309-
return false;
304+
entryCount++;
305+
currentTimestamp += timestampIncrement;
306+
}
307+
308+
// Upload batch
309+
var success = await _wrapper.PutLogEventsAsync(_logGroupName, _logStreamName, logEvents);
310+
if (!success)
311+
{
312+
_logger.LogError($"Failed to upload batch {batchNum + 1}/{numBatches}");
313+
return false;
314+
}
315+
316+
Console.WriteLine($"Uploaded batch {batchNum + 1}/{numBatches}");
310317
}
311318

312-
var startMatch = Regex.Match(output, @"QUERY_START_DATE=(\d+)");
313-
var endMatch = Regex.Match(output, @"QUERY_END_DATE=(\d+)");
319+
// Set query date range (convert milliseconds to seconds for query API)
320+
_queryStartDate = startTimeMs / 1000;
321+
_queryEndDate = (currentTimestamp - timestampIncrement) / 1000;
314322

315-
if (startMatch.Success && endMatch.Success)
316-
{
317-
_queryStartDate = long.Parse(startMatch.Groups[1].Value) / 1000;
318-
_queryEndDate = long.Parse(endMatch.Groups[1].Value) / 1000;
323+
Console.WriteLine($"Query start date: {DateTimeOffset.FromUnixTimeSeconds(_queryStartDate):yyyy-MM-ddTHH:mm:ss.fffZ}");
324+
Console.WriteLine($"Query end date: {DateTimeOffset.FromUnixTimeSeconds(_queryEndDate):yyyy-MM-ddTHH:mm:ss.fffZ}");
325+
Console.WriteLine($"Successfully uploaded {totalEntries} log entries");
319326

320-
Console.WriteLine($"Query start date: {DateTimeOffset.FromUnixTimeSeconds(_queryStartDate):yyyy-MM-ddTHH:mm:ss.fffZ}");
321-
Console.WriteLine($"Query end date: {DateTimeOffset.FromUnixTimeSeconds(_queryEndDate):yyyy-MM-ddTHH:mm:ss.fffZ}");
322-
return true;
323-
}
324-
else
325-
{
326-
_logger.LogError("Failed to parse timestamps from script output.");
327-
return false;
328-
}
327+
return true;
329328
}
330329
catch (Exception ex)
331330
{
@@ -342,7 +341,7 @@ public static async Task ExecuteLargeQuery()
342341
Console.WriteLine("Starting recursive query to retrieve all logs...");
343342
Console.WriteLine();
344343

345-
var queryLimit = PromptUserForInteger("Enter the query limit (default 10000, max 10000): ", 10000);
344+
var queryLimit = PromptUserForInteger("Enter the query limit (max 10000) ", 10000);
346345
if (queryLimit > 10000) queryLimit = 10000;
347346

348347
var queryString = "fields @timestamp, @message | sort @timestamp asc";
@@ -407,17 +406,70 @@ private static async Task<List<List<ResultField>>> PerformLargeQuery(
407406
return results;
408407
}
409408

409+
// Parse the timestamp - CloudWatch returns ISO 8601 format with milliseconds
410410
var lastTime = DateTimeOffset.Parse(lastTimestamp).ToUnixTimeSeconds();
411+
412+
// Check if there's any time range left to query
413+
if (lastTime >= endTime)
414+
{
415+
return results;
416+
}
417+
418+
// Calculate midpoint between last result and end time
411419
var midpoint = (lastTime + endTime) / 2;
420+
421+
// Ensure we have enough range to split
422+
if (midpoint <= lastTime || midpoint >= endTime)
423+
{
424+
// Range too small to split, just query the remaining range
425+
var remainingResults = await PerformLargeQuery(logGroupName, queryString, lastTime, endTime, limit);
426+
427+
var allResults = new List<List<ResultField>>(results);
428+
// Skip the first result if it's a duplicate of the last result from previous query
429+
if (remainingResults.Count > 0)
430+
{
431+
var firstTimestamp = remainingResults[0].Find(f => f.Field == "@timestamp")?.Value;
432+
if (firstTimestamp == lastTimestamp)
433+
{
434+
remainingResults.RemoveAt(0);
435+
}
436+
}
437+
allResults.AddRange(remainingResults);
438+
return allResults;
439+
}
412440

441+
// Split the remaining range in half
413442
var results1 = await PerformLargeQuery(logGroupName, queryString, lastTime, midpoint, limit);
414443
var results2 = await PerformLargeQuery(logGroupName, queryString, midpoint, endTime, limit);
415444

416-
var allResults = new List<List<ResultField>>(results);
417-
allResults.AddRange(results1);
418-
allResults.AddRange(results2);
445+
var combinedResults = new List<List<ResultField>>(results);
446+
447+
// Remove duplicate from results1 if it matches the last result
448+
if (results1.Count > 0)
449+
{
450+
var firstTimestamp1 = results1[0].Find(f => f.Field == "@timestamp")?.Value;
451+
if (firstTimestamp1 == lastTimestamp)
452+
{
453+
results1.RemoveAt(0);
454+
}
455+
}
456+
457+
combinedResults.AddRange(results1);
458+
459+
// Remove duplicate from results2 if it matches the last result from results1
460+
if (results2.Count > 0 && results1.Count > 0)
461+
{
462+
var lastTimestamp1 = results1[results1.Count - 1].Find(f => f.Field == "@timestamp")?.Value;
463+
var firstTimestamp2 = results2[0].Find(f => f.Field == "@timestamp")?.Value;
464+
if (firstTimestamp2 == lastTimestamp1)
465+
{
466+
results2.RemoveAt(0);
467+
}
468+
}
469+
470+
combinedResults.AddRange(results2);
419471

420-
return allResults;
472+
return combinedResults;
421473
}
422474

423475
/// <summary>
@@ -592,9 +644,9 @@ private static bool GetYesNoResponse(string question)
592644
/// </summary>
593645
private static string PromptUserForStackName()
594646
{
595-
Console.WriteLine($"Enter a name for the CloudFormation stack (default: {_stackName}): ");
596647
if (_interactive)
597648
{
649+
Console.Write($"Enter a name for the CloudFormation stack (press Enter for default '{_stackName}'): ");
598650
string? input = Console.ReadLine();
599651
if (!string.IsNullOrWhiteSpace(input))
600652
{
@@ -617,7 +669,7 @@ private static string PromptUserForInput(string prompt, string defaultValue)
617669
{
618670
if (_interactive)
619671
{
620-
Console.Write(prompt);
672+
Console.Write($"{prompt}(press Enter for default '{defaultValue}'): ");
621673
string? input = Console.ReadLine();
622674
return string.IsNullOrWhiteSpace(input) ? defaultValue : input;
623675
}
@@ -631,7 +683,7 @@ private static int PromptUserForInteger(string prompt, int defaultValue)
631683
{
632684
if (_interactive)
633685
{
634-
Console.Write(prompt);
686+
Console.Write($"{prompt}(press Enter for default '{defaultValue}'): ");
635687
string? input = Console.ReadLine();
636688
if (string.IsNullOrWhiteSpace(input) || !int.TryParse(input, out var result))
637689
{
@@ -659,4 +711,4 @@ private static long PromptUserForLong(string prompt)
659711
return 0;
660712
}
661713
}
662-
// snippet-end:[CloudWatchLogs.dotnetv3.LargeQueryWorkflow]
714+
// snippet-end:[CloudWatchLogs.dotnetv4.LargeQueryWorkflow]

0 commit comments

Comments
 (0)