Skip to content

Commit 77fd05f

Browse files
committed
fix: resolve unified config edge cases
1 parent 9350f22 commit 77fd05f

6 files changed

Lines changed: 148 additions & 11 deletions

File tree

cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -769,8 +769,9 @@ func prepareContainerSettings(
769769
) (*azdext.ServiceConfig, error) {
770770
rawAdditional := svc.GetAdditionalProperties()
771771
rawConfig := svc.GetConfig()
772-
hasRootFileRef := rawAdditional.GetFields()["$ref"] != nil ||
773-
rawConfig.GetFields()["$ref"] != nil
772+
hasRootFileRef := rawAdditional != nil &&
773+
rawAdditional.GetFields()["$ref"] != nil ||
774+
rawConfig != nil && rawConfig.GetFields()["$ref"] != nil
774775
if hasRootFileRef {
775776
if err := project.ResolveServiceConfigInPlace(
776777
svc,
@@ -781,6 +782,11 @@ func prepareContainerSettings(
781782
err,
782783
)
783784
}
785+
} else if err := project.NormalizeServiceConfigInPlace(svc); err != nil {
786+
return nil, fmt.Errorf(
787+
"failed to normalize agent config: %w",
788+
err,
789+
)
784790
}
785791
foundryAgentConfig, err := project.LoadServiceTargetAgentConfig(svc)
786792
if err != nil {

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,47 @@ func TestPrepareContainerSettings_PreservesNestedFileRef(t *testing.T) {
212212
require.Equal(t, project.DefaultMemory, cfg.Container.Resources.Memory)
213213
}
214214

215+
func TestPrepareContainerSettings_NormalizesInlineEnvironment(t *testing.T) {
216+
t.Parallel()
217+
218+
props, err := structpb.NewStruct(map[string]any{
219+
"kind": "hosted",
220+
"name": "echo",
221+
"env": map[string]any{
222+
"ENABLED": true,
223+
},
224+
})
225+
require.NoError(t, err)
226+
svc := &azdext.ServiceConfig{
227+
Name: "echo",
228+
Host: AiAgentHost,
229+
AdditionalProperties: props,
230+
}
231+
232+
persisted, err := prepareContainerSettings(svc, t.TempDir())
233+
234+
require.NoError(t, err)
235+
require.Equal(
236+
t,
237+
"true",
238+
persisted.GetAdditionalProperties().
239+
GetFields()["env"].GetStructValue().
240+
GetFields()["ENABLED"].GetStringValue(),
241+
)
242+
}
243+
244+
func TestPrepareContainerSettings_WithoutProperties(t *testing.T) {
245+
t.Parallel()
246+
247+
persisted, err := prepareContainerSettings(
248+
&azdext.ServiceConfig{Name: "echo", Host: AiAgentHost},
249+
t.TempDir(),
250+
)
251+
252+
require.NoError(t, err)
253+
require.NotNil(t, persisted)
254+
}
255+
215256
func TestKindEnvUpdateRejectsTraversal(t *testing.T) {
216257
t.Parallel()
217258

cli/azd/extensions/azure.ai.agents/internal/project/agent_definition.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,28 @@ func ResolveServiceConfigInPlace(
399399
return nil
400400
}
401401

402+
// NormalizeServiceConfigInPlace converts environment scalars without
403+
// resolving nested file references.
404+
func NormalizeServiceConfigInPlace(svc *azdext.ServiceConfig) error {
405+
if props := svc.GetAdditionalProperties(); props != nil &&
406+
len(props.GetFields()) > 0 {
407+
normalized, err := normalizeServiceProps(props, svc.GetName())
408+
if err != nil {
409+
return err
410+
}
411+
svc.AdditionalProperties = normalized
412+
}
413+
if config := svc.GetConfig(); config != nil &&
414+
len(config.GetFields()) > 0 {
415+
normalized, err := normalizeServiceProps(config, svc.GetName())
416+
if err != nil {
417+
return err
418+
}
419+
svc.Config = normalized
420+
}
421+
return nil
422+
}
423+
402424
func hydrateResolvedServiceFields(
403425
svc *azdext.ServiceConfig,
404426
props *structpb.Struct,
@@ -450,6 +472,29 @@ func resolveServiceProps(
450472
return out, nil
451473
}
452474

475+
func normalizeServiceProps(
476+
props *structpb.Struct,
477+
serviceName string,
478+
) (*structpb.Struct, error) {
479+
values := props.AsMap()
480+
if err := projectconfig.NormalizeEnvironment(values); err != nil {
481+
return nil, fmt.Errorf(
482+
"normalizing service %q environment: %w",
483+
serviceName,
484+
err,
485+
)
486+
}
487+
out, err := structpb.NewStruct(values)
488+
if err != nil {
489+
return nil, fmt.Errorf(
490+
"encoding normalized service %q config: %w",
491+
serviceName,
492+
err,
493+
)
494+
}
495+
return out, nil
496+
}
497+
453498
// UpsertAgentEnvVars adds or updates environment variables on the agent
454499
// definition carried inline on the service entry, preserving every other key.
455500
// It is used by commands that mutate the definition (e.g. `optimize apply`).

cli/azd/extensions/azure.ai.agents/internal/project/foundry_provisioning_provider.go

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,17 @@ func (p *FoundryProvisioningProvider) Initialize(
173173
)
174174
}
175175
if endpoint != "" {
176-
warnNetworkIgnoredInBrownfield(rawYAML, svcName)
176+
if err := warnNetworkIgnoredInBrownfield(
177+
rawYAML,
178+
projectPath,
179+
svcName,
180+
); err != nil {
181+
return exterrors.Validation(
182+
exterrors.CodeInvalidAzureYaml,
183+
fmt.Sprintf("resolve Foundry service configuration: %s", err),
184+
"fix the project service configuration in azure.yaml",
185+
)
186+
}
177187
p.brownfieldEndpoint = endpoint
178188
if err := p.captureBrownfieldDeployments(ctx, rawYAML, svcName); err != nil {
179189
return err
@@ -194,7 +204,17 @@ func (p *FoundryProvisioningProvider) Initialize(
194204
case errors.Is(err, synthesis.ErrEndpointBrownfield):
195205
// endpoint: reuse — connect to the existing project, skip provisioning.
196206
// network: has no effect in brownfield mode; warn if both are present.
197-
warnNetworkIgnoredInBrownfield(rawYAML, svcName)
207+
if err := warnNetworkIgnoredInBrownfield(
208+
rawYAML,
209+
projectPath,
210+
svcName,
211+
); err != nil {
212+
return exterrors.Validation(
213+
exterrors.CodeInvalidAzureYaml,
214+
fmt.Sprintf("resolve Foundry service configuration: %s", err),
215+
"fix the project service configuration in azure.yaml",
216+
)
217+
}
198218
endpoint, endpointErr := foundryServiceEndpointAtRoot(
199219
rawYAML,
200220
projectPath,
@@ -286,23 +306,46 @@ func (p *FoundryProvisioningProvider) networkEnvMap(ctx context.Context) map[str
286306
// warnNetworkIgnoredInBrownfield logs a warning when a service declares both
287307
// endpoint: (brownfield) and network:. The account's network posture is fixed
288308
// by whoever created it, so the network: block has no effect.
289-
func warnNetworkIgnoredInBrownfield(rawYAML []byte, svcName string) {
309+
func warnNetworkIgnoredInBrownfield(
310+
rawYAML []byte,
311+
projectRoot string,
312+
svcName string,
313+
) error {
290314
type svc struct {
291315
Endpoint string `yaml:"endpoint,omitempty"`
292316
Network yaml.Node `yaml:"network,omitempty"`
293317
}
294318
type root struct {
295-
Services map[string]svc `yaml:"services"`
319+
Services map[string]map[string]any `yaml:"services"`
296320
}
297321
var r root
298322
if err := yaml.Unmarshal(rawYAML, &r); err != nil {
299-
return
323+
return err
324+
}
325+
values := r.Services[svcName]
326+
if values == nil {
327+
return nil
328+
}
329+
if projectRoot != "" {
330+
resolved, err := foundry.ResolveFileRefs(values, projectRoot)
331+
if err != nil {
332+
return err
333+
}
334+
values = resolved
300335
}
301-
s := r.Services[svcName]
302-
if strings.TrimSpace(s.Endpoint) != "" && !s.Network.IsZero() {
336+
data, err := yaml.Marshal(values)
337+
if err != nil {
338+
return err
339+
}
340+
var service svc
341+
if err := yaml.Unmarshal(data, &service); err != nil {
342+
return err
343+
}
344+
if strings.TrimSpace(service.Endpoint) != "" && !service.Network.IsZero() {
303345
log.Printf("[warn] foundry provider: service %q sets both endpoint: and network:; "+
304346
"network: is ignored in brownfield mode (the account's network posture is fixed)", svcName)
305347
}
348+
return nil
306349
}
307350

308351
// or infra/main.bicep exists under p.projectPath. Stat-only.

cli/azd/extensions/azure.ai.agents/schemas/Agent.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"env": {
1919
"type": "object",
2020
"description": "Environment variables for the agent runtime. Values may use ${VAR} (azd env) or ${{...}} (Foundry server-side resolution).",
21-
"additionalProperties": { "type": "string" }
21+
"additionalProperties": {
22+
"type": ["string", "boolean", "number", "null"]
23+
}
2224
},
2325
"toolboxes": {
2426
"type": "array",

cli/azd/extensions/azure.ai.agents/schemas/azure.ai.agent.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"type": "object",
99
"description": "Environment variables as key-value pairs",
1010
"additionalProperties": {
11-
"type": "string"
11+
"type": ["string", "boolean", "number", "null"]
1212
}
1313
},
1414
"container": {

0 commit comments

Comments
 (0)