Skip to content

Instantly share code, notes, and snippets.

@joperezr
Created March 11, 2025 21:11
Show Gist options
  • Save joperezr/5260cf4d1dddc9c89804e38a67b87ffc to your computer and use it in GitHub Desktop.
Save joperezr/5260cf4d1dddc9c89804e38a67b87ffc to your computer and use it in GitHub Desktop.
Application that helps updating dotnet/aspire repository docker image tag references to latest versions
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.ComponentModel;
using System.Data.Common;
using System.Text.Json.Serialization;
var builder = Host.CreateApplicationBuilder(args);
builder.Configuration.AddUserSecrets<Program>();
Uri? ollamaEndpoint = null;
string? modelName = null;
if (builder.Configuration.GetConnectionString("deepseek") is string connectionString)
{
var connectionBuilder = new DbConnectionStringBuilder
{
ConnectionString = connectionString
};
if (connectionBuilder.ContainsKey("Endpoint") && Uri.TryCreate(connectionBuilder["Endpoint"].ToString(), UriKind.Absolute, out Uri? endpoint))
{
ollamaEndpoint = endpoint;
}
if (connectionBuilder.ContainsKey("Model"))
{
modelName = (string)connectionBuilder["Model"];
}
}
// Code for GH Copilot
// builder.Services.AddSingleton(
// new ChatCompletionsClient(
// new("https://models.inference.ai.azure.com"),
// new AzureKeyCredential(builder.Configuration.GetValue<string>("GH_TOKEN")!)));
// builder.Services.AddLogging(b => b.AddConsole().SetMinimumLevel(LogLevel.Warning));
// builder.Services.AddChatClient(services => services.GetRequiredService<ChatCompletionsClient>().AsChatClient("gpt-4o"))
// .UseFunctionInvocation()
// .UseLogging();
builder.Services.AddChatClient(new OllamaChatClient(ollamaEndpoint!, modelName!))
.UseLogging();
var app = builder.Build();
var client = app.Services.GetRequiredService<IChatClient>();
string basePrompt = @"You are an expert assistant in software versioning and DevOps. Your task is to analyze a C# source file that contains Docker image tag references and determine if any of these tag references need to be updated. Follow these instructions:
1. **Input Parsing:**
You are provided with the full contents of a C# file that includes one or more Docker image tag references. For each Docker image reference, identify:
- The registry (e.g., ""docker.io"").
- The image name (e.g., ""library/mysql"" or ""library/phpmyadmin"").
- The tag currently in use (e.g., ""9.1"", ""v4.5"", ""5.2"", or ""latest"").
- The associated triple-slash comment line that includes a remarks section indicating the tag value (e.g., ""/// <remarks>9.1</remarks>"").
2. **Tag Filtering and Normalization:**
- Process tags that contain version information, which may be numeric or prefixed with a character such as 'v' (for example, ""v4.5"" should be treated as version 4.5).
- If the tag does not contain any recognizable version information (for example, ""latest""), do not consider it for an update.
3. **Fetching Available Tags:**
For each Docker image with a version tag, call a provided function that accepts the registry and image name and returns a string listing all available tag names for that image.
4. **Version Comparison and Update Decision:**
For each image reference with a version tag:
- Parse the available tag names using versioning semantics, normalizing tags that start with 'v' by removing the prefix for comparison.
- For tags in a two-part format (e.g., ""9.1"" or ""v9.1""), assume that if a newer patch version (like ""9.1.5"") exists, it is already covered by the two-part tag. Do not update in this case.
- Update the tag only if there is a clearly newer version with an updated minor version (for example, moving from ""9.1"" to any of ""9.2"", ""9.2.0"", ""9.2.1"", or ""9.2.3"", or from ""v4.5"" to ""v4.7"").
- Do not update if the only candidates are prerelease tags (e.g., those containing ""preview"" or similar identifiers).
5. **Generating the Updated File:**
If any version tag requires updating based on the above rules:
- Generate a new version of the file where the tag constants are updated to the latest appropriate two-part version (preserving the original formatting, including any 'v' prefix).
- Update the corresponding triple-slash comment line (the remarks section) to match the new tag value.
Otherwise, leave the file unchanged.
6. **Final Output:**
Your final response must be a JSON object with exactly two properties:
- ""update_required"": a boolean that is true if any tag updates are needed, or false if all tags are already up-to-date.
- ""updated_file"": a string containing the full contents of the C# file with updated tag references and remarks if updates were required. If no updates are needed, return the original file contents.
**Important:**
- Do not include any text or explanation outside the JSON object.
- Ensure the JSON output is valid and contains only the two specified properties.
Please analyze the provided file and return your answer as a JSON object according to these instructions.";
var chatOptions = new ChatOptions
{
Tools = [AIFunctionFactory.Create(GetListOfImageTags)]
};
var imageTagFiles = Directory.GetFiles("/home/joperezr/aspire/src", "*ImageTags.cs", SearchOption.AllDirectories);
foreach (var file in imageTagFiles)
{
try{
var code = File.ReadAllText(file);
var update = await client.GetResponseAsync<UpdateResponse>(
[
new ChatMessage(Microsoft.Extensions.AI.ChatRole.System, basePrompt),
new ChatMessage(Microsoft.Extensions.AI.ChatRole.User, "Help me determine if the docker image tags in this file need to be updated.\n\n```csharp\n" + code + "\n```"),
], chatOptions
);
if (update.Result.UpdateRequired)
{
File.WriteAllText(file, update.Result.UpdatedFile);
}
}
catch (Exception e)
{
Console.WriteLine($"Error when parsing file {file}:\n{e.Message}");
}
}
[Description("Given a registry and image name representing a docker image, returns a string listing all available tag names for that image.")]
static string GetListOfImageTags([Description("The registry of the docker image")]string registry, [Description("The image name of the docker image")]string imageName)
{
using var process = new System.Diagnostics.Process();
process.StartInfo.FileName = "/home/joperezr/list-tags.sh";
process.StartInfo.Arguments = $"{registry}/{imageName}";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output;
}
public class UpdateResponse
{
[JsonPropertyName("update_required")]
public bool UpdateRequired { get; set; }
[JsonPropertyName("updated_file")]
public string UpdatedFile { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment