Skip to content

Commit 124cad0

Browse files
eerhardtCopilot
andauthored
Validate environment variable names for Foundry hosted agents (#16884)
* Validate environment variable names for Foundry hosted agents Foundry Hosted Agents only support environment variable names containing ASCII letters, digits, and underscores. Service discovery names (with dashes/dots) and other generated names would silently produce invalid agent versions at deploy time. Add validation in HostedAgentConfiguration.ToProjectsAgentVersionCreationOptions using a source-generated regex; throw DistributedApplicationException with the offending names if any are invalid. Rename target resources in the existing publish test to non-dashed names so the test exercises the happy path. Fixes #16858 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * PR feedback --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3fcc4f7 commit 124cad0

4 files changed

Lines changed: 48 additions & 7 deletions

File tree

src/Aspire.Hosting.Foundry/HostedAgent/AzureHostedAgentResource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ private async Task<ProjectsAgentVersion> DeployAsync(PipelineStepContext context
157157
var projectClient = new AIProjectClient(new Uri(projectEndpoint), credential);
158158
var result = await projectClient.AgentAdministrationClient.CreateAgentVersionAsync(
159159
Name,
160-
def.ToProjectsAgentVersionCreationOptions(),
160+
def.ToProjectsAgentVersionCreationOptions(Target.Name),
161161
cancellationToken: context.CancellationToken
162162
).ConfigureAwait(false);
163163

src/Aspire.Hosting.Foundry/HostedAgent/HostedAgentConfiguration.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Globalization;
5+
using System.Text.RegularExpressions;
56
using Azure.AI.Projects.Agents;
67

78
namespace Aspire.Hosting.Foundry;
@@ -15,7 +16,7 @@ namespace Aspire.Hosting.Foundry;
1516
/// ATS-compatible properties are exported; Azure SDK-specific members remain .NET-only.
1617
/// </remarks>
1718
[AspireExport(ExposeProperties = true)]
18-
public class HostedAgentConfiguration(string image)
19+
public partial class HostedAgentConfiguration(string image)
1920
{
2021
/// <summary>
2122
/// The description of the hosted agent.
@@ -112,8 +113,10 @@ public decimal Memory
112113
/// <summary>
113114
/// Converts this configuration to an <see cref="ProjectsAgentVersionCreationOptions"/> instance.
114115
/// </summary>
115-
public ProjectsAgentVersionCreationOptions ToProjectsAgentVersionCreationOptions()
116+
internal ProjectsAgentVersionCreationOptions ToProjectsAgentVersionCreationOptions(string targetResourceName)
116117
{
118+
ValidateEnvironmentVariableNames(EnvironmentVariables.Keys, targetResourceName);
119+
117120
var def = new HostedAgentDefinition(
118121
ContainerProtocolVersions,
119122
cpu: CpuString,
@@ -143,4 +146,26 @@ public ProjectsAgentVersionCreationOptions ToProjectsAgentVersionCreationOptions
143146
}
144147
return options;
145148
}
149+
150+
private static void ValidateEnvironmentVariableNames(IEnumerable<string> environmentVariableNames, string? targetResourceName)
151+
{
152+
var invalidNames = environmentVariableNames
153+
.Where(static name => !EnvironmentVariableNameRegex().IsMatch(name))
154+
.Order(StringComparer.Ordinal)
155+
.ToArray();
156+
157+
if (invalidNames.Length == 0)
158+
{
159+
return;
160+
}
161+
162+
throw new DistributedApplicationException(
163+
$"Foundry hosted agent for target resource '{targetResourceName}' contains environment variable names that are not supported by Foundry Hosted Agents. " +
164+
$"Environment variable names must contain only ASCII letters, digits, or underscores. " +
165+
$"Invalid name(s): '{string.Join("', '", invalidNames)}'");
166+
}
167+
168+
// hosted agent environment variables must contain only letters, digits, or underscores.
169+
[GeneratedRegex("^[A-Za-z0-9_]+$")]
170+
private static partial Regex EnvironmentVariableNameRegex();
146171
}

tests/Aspire.Hosting.Azure.Tests/FoundryExtensionsTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,11 @@ public async Task PublishAsHostedAgent_ResolvesExternalContainerAppReference()
288288
var project = builder.AddFoundry("account")
289289
.AddProject("my-project");
290290

291-
var weatherAgent = builder.AddProject<Project>("weather-agent", launchProfileName: null)
291+
var weatherAgent = builder.AddProject<Project>("weatheragent", launchProfileName: null)
292292
.WithEndpoint(targetPort: 9000, scheme: "http", name: "http", isExternal: true)
293293
.WithComputeEnvironment(env);
294294

295-
var advisorAgent = builder.AddProject<Project>("advisor-agent", launchProfileName: null)
295+
var advisorAgent = builder.AddProject<Project>("advisoragent", launchProfileName: null)
296296
.WithReference(weatherAgent)
297297
.WaitFor(weatherAgent)
298298
.PublishAsHostedAgent(project);
@@ -314,7 +314,7 @@ public async Task PublishAsHostedAgent_ResolvesExternalContainerAppReference()
314314
NullLogger<FoundryExtensionsTests>.Instance,
315315
cts.Token);
316316

317-
Assert.Equal("https://weather-agent.example.azurecontainerapps.io", environmentVariables["services__weather-agent__http__0"]);
317+
Assert.Equal("https://weatheragent.example.azurecontainerapps.io", environmentVariables["services__weatheragent__http__0"]);
318318
}
319319

320320
[Fact]
@@ -469,3 +469,4 @@ private sealed class Project : IProjectMetadata
469469
}
470470

471471
}
472+

tests/Aspire.Hosting.Foundry.Tests/HostedAgentConfigurationTests.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public void ToProjectsAgentVersionCreationOptions_ProducesValidOptions()
6767
Cpu = 1.0m,
6868
};
6969

70-
var options = config.ToProjectsAgentVersionCreationOptions();
70+
var options = config.ToProjectsAgentVersionCreationOptions("target");
7171

7272
Assert.NotNull(options);
7373
Assert.Equal("Test agent", options.Description);
@@ -83,6 +83,21 @@ public void EnvironmentVariables_CanBeAdded()
8383
Assert.Equal("VALUE", config.EnvironmentVariables["KEY"]);
8484
}
8585

86+
[Fact]
87+
public void ToProjectsAgentVersionCreationOptions_ThrowsForInvalidEnvironmentVariableNames()
88+
{
89+
var config = new HostedAgentConfiguration("myimage:latest");
90+
config.EnvironmentVariables["VALID_NAME_1"] = "value";
91+
config.EnvironmentVariables["INVALID-NAME"] = "value";
92+
config.EnvironmentVariables["invalid.name"] = "value";
93+
94+
var ex = Assert.Throws<DistributedApplicationException>(() => config.ToProjectsAgentVersionCreationOptions("target"));
95+
96+
Assert.Equal(
97+
"Foundry hosted agent for target resource 'target' contains environment variable names that are not supported by Foundry Hosted Agents. Environment variable names must contain only ASCII letters, digits, or underscores. Invalid name(s): 'INVALID-NAME', 'invalid.name'",
98+
ex.Message);
99+
}
100+
86101
[Fact]
87102
public void DefaultMetadata_ContainsDeployedByAndOn()
88103
{

0 commit comments

Comments
 (0)