Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ const (
envKeyPrincipalID = "AZURE_PRINCIPAL_ID"
)

// deploymentNamePrefix is prepended to the azd environment name to form
// the ARM deployment name so re-runs update the same deployment record.
const deploymentNamePrefix = "azd-foundry-"
const (
// deploymentNamePrefix is prepended to the azd environment name so re-runs
// update the same ARM deployment record.
deploymentNamePrefix = "azd-foundry-"
maxDeploymentNameLength = 64
)

// FoundryProvisioningProvider implements azdext.ProvisioningProvider for
// the service whose host is FoundryProjectHost. By default it deploys
Expand Down Expand Up @@ -1791,28 +1794,35 @@ func (p *FoundryProvisioningProvider) deploymentsClient(ctx context.Context) (*a
return factory.NewDeploymentsClient(), nil
}

// deploymentName is stable per azd env so re-runs update one record, plus a
// short hash of the project path so two projects sharing an env name (e.g.
// "dev") in the same subscription don't write the same deployment and read each
// other's outputs.
// deploymentName is stable per azd env and capped at ARM's 64-character limit.
// The project-path hash separates projects sharing an env name; an env-name hash
// preserves that identity when the readable env-name segment must be truncated.
func (p *FoundryProvisioningProvider) deploymentName() string {
h := fnv.New32a()
_, _ = h.Write([]byte(p.projectPath))
return fmt.Sprintf("%s%s-%08x", deploymentNamePrefix, p.envName, h.Sum32())
pathHash := fnv.New32a()
_, _ = pathHash.Write([]byte(p.projectPath))
name := fmt.Sprintf("%s%s-%08x", deploymentNamePrefix, p.envName, pathHash.Sum32())
if len(name) <= maxDeploymentNameLength {
return name
}

envHash := fnv.New32a()
_, _ = envHash.Write([]byte(p.envName))
hashTail := fmt.Sprintf("-%08x-%08x", envHash.Sum32(), pathHash.Sum32())
keep := maxDeploymentNameLength - len(deploymentNamePrefix) - len(hashTail)
return deploymentNamePrefix + p.envName[:keep] + hashTail
}

// brownfieldDeploymentName is deploymentName plus a "-brownfield" suffix, capped
// at ARM's 64-character deployment-name limit. The trailing path hash and suffix
// are preserved (uniqueness and intent); only the env-name portion is truncated.
func (p *FoundryProvisioningProvider) brownfieldDeploymentName() string {
const maxLen = 64
name := p.deploymentName() + "-brownfield"
if len(name) <= maxLen {
if len(name) <= maxDeploymentNameLength {
return name
}
const suffix = "-brownfield"
hashTail := name[len(name)-len(suffix)-9 : len(name)-len(suffix)] // "-<8 hex>"
keep := maxLen - len(hashTail) - len(suffix)
keep := maxDeploymentNameLength - len(hashTail) - len(suffix)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greenfield deploymentName() now appends an env-name hash so two long env names in the same project that truncate to the same readable prefix stay distinct. This brownfield path drops that hash: it keeps name[:keep] (the readable prefix) plus the path-hash tail and -brownfield, so two long env names that share their first ~32 characters in the same project (say foo-...-dev and foo-...-test) still collide here and would write the same RG-scoped deployment record. It's pre-existing, not introduced by this PR, but since the new env-name hash exists precisely for this disambiguation, is extending it to the brownfield name in scope, or intentionally deferred?

return name[:keep] + hashTail + suffix
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ func TestArmInputsToProto(t *testing.T) {
func TestDeploymentName_StableForEnv(t *testing.T) {
p := &FoundryProvisioningProvider{envName: "dev", projectPath: "/proj/a"}
first := p.deploymentName()
assert.Equal(t, "azd-foundry-dev-accd5375", first, "names that already fit must not change")
assert.Equal(t, first, p.deploymentName(), "same env+path is stable")
assert.True(t, strings.HasPrefix(first, "azd-foundry-dev-"), "carries env and discriminator")

Expand All @@ -421,6 +422,25 @@ func TestDeploymentName_StableForEnv(t *testing.T) {
assert.NotEqual(t, first, other.deploymentName())
}

func TestDeploymentName_LongEnvironmentName(t *testing.T) {
projectPath := `C:\Users\zhihuan\source\repos\agent-framework-egress-control-responses`
p := &FoundryProvisioningProvider{
envName: "agent-framework-egress-control-responses-dev",
projectPath: projectPath,
}

name := p.deploymentName()
assert.Len(t, name, maxDeploymentNameLength)
assert.Equal(t, "azd-foundry-agent-framework-egress-control-res-269fe92a-aa9d4054", name)

// Environment names that differ only beyond the retained prefix must not collide.
other := &FoundryProvisioningProvider{
envName: "agent-framework-egress-control-responses-test",
projectPath: projectPath,
}
assert.NotEqual(t, name, other.deploymentName())
}

func TestDeploymentOutputsResources_NilSafe(t *testing.T) {
assert.Nil(t, deploymentOutputs(nil))
assert.Nil(t, deploymentResources(nil))
Expand Down
Loading