Skip to content

extension: support existing resource for ai build start #5193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 9, 2025
Merged
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
2 changes: 1 addition & 1 deletion cli/azd/.vscode/cspell-azd-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ flexconsumption
Frontends
fsnotify
funcapp
functestapp
functionapp
gjson
go-imath
Expand Down Expand Up @@ -180,6 +179,7 @@ pflag
pgadmin
posix
preinit
protogen
proxying
psanford
psycopg
Expand Down
1 change: 1 addition & 0 deletions cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,4 @@ overrides:
ignorePaths:
- "**/*_test.go"
- "**/mock*.go"
- "**/*.pb.go"
36 changes: 23 additions & 13 deletions cli/azd/extensions/microsoft.azd.ai.builder/internal/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,9 @@ func (a *startAction) Run(ctx context.Context, args []string) error {
if a.scenarioData.DatabaseType != "" {
desiredName := strings.ReplaceAll(a.scenarioData.DatabaseType, "db.", "")
dbResource := &azdext.ComposedResource{
Name: a.generateResourceName(desiredName),
Type: a.scenarioData.DatabaseType,
Name: a.generateResourceName(desiredName),
Type: a.scenarioData.DatabaseType,
ResourceId: a.scenarioData.DatabaseId,
}
resourcesToAdd[dbResource.Name] = dbResource
}
Expand All @@ -214,17 +215,19 @@ func (a *startAction) Run(ctx context.Context, args []string) error {
if a.scenarioData.MessagingType != "" {
desiredName := strings.ReplaceAll(a.scenarioData.MessagingType, "messaging.", "")
messagingResource := &azdext.ComposedResource{
Name: a.generateResourceName(desiredName),
Type: a.scenarioData.MessagingType,
Name: a.generateResourceName(desiredName),
Type: a.scenarioData.MessagingType,
ResourceId: a.scenarioData.MessagingId,
}
resourcesToAdd[messagingResource.Name] = messagingResource
}

// Add vector store resources
if a.scenarioData.VectorStoreType != "" {
vectorStoreResource := &azdext.ComposedResource{
Name: a.generateResourceName("vector-store"),
Type: a.scenarioData.VectorStoreType,
Name: a.generateResourceName("vector-store"),
Type: a.scenarioData.VectorStoreType,
ResourceId: a.scenarioData.VectorStoreId,
}
resourcesToAdd[vectorStoreResource.Name] = vectorStoreResource
}
Expand All @@ -244,9 +247,10 @@ func (a *startAction) Run(ctx context.Context, args []string) error {
}

storageResource := &azdext.ComposedResource{
Name: a.generateResourceName("storage"),
Type: "storage",
Config: storageConfigJson,
Name: a.generateResourceName("storage"),
Type: "storage",
Config: storageConfigJson,
ResourceId: a.scenarioData.StorageAccountId,
}

resourcesToAdd[storageResource.Name] = storageResource
Expand Down Expand Up @@ -342,11 +346,16 @@ func (a *startAction) Run(ctx context.Context, args []string) error {
return fmt.Errorf("failed to marshal app config: %w", err)
}

resourceId := ""
if a.scenarioData.AppResourceIds[i] != "new" {
resourceId = a.scenarioData.AppResourceIds[i]
}
appResource := &azdext.ComposedResource{
Name: a.generateResourceName(appKey),
Type: appType,
Config: appConfigJson,
Uses: []string{},
Name: a.generateResourceName(appKey),
Type: appType,
Config: appConfigJson,
Uses: []string{},
ResourceId: resourceId,
}

serviceName := a.generateServiceName(appKey)
Expand Down Expand Up @@ -423,6 +432,7 @@ func (a *startAction) Run(ctx context.Context, args []string) error {
resourceUseMap[dependentResource.Type] = struct{}{}
}
}
// Existing resources that are already in azure.yaml
for _, existingResource := range a.composedResources {
// Skip if the resource type is already added.
if _, has := resourceUseMap[existingResource.Type]; has {
Expand Down
1 change: 1 addition & 0 deletions cli/azd/grpc/proto/compose.proto
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ message ComposedResource {
string type = 2;
bytes config = 3;
repeated string uses = 4;
string resource_id = 5;
}

// ComposedResourceType represents a type of composability resource.
Expand Down
52 changes: 43 additions & 9 deletions cli/azd/internal/grpcserver/compose_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
"fmt"

"github.com/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
"github.com/azure/azure-dev/cli/azd/pkg/infra"
"github.com/azure/azure-dev/cli/azd/pkg/lazy"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"google.golang.org/grpc/codes"
Expand All @@ -19,15 +21,20 @@ import (
// composeService exposes features of the AZD composability model to the Extensions Framework layer.
type composeService struct {
azdext.UnimplementedComposeServiceServer

lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext]
lazyEnv *lazy.Lazy[*environment.Environment]
lazyEnvManger *lazy.Lazy[environment.Manager]
}

func NewComposeService(
lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext],
lazyEnv *lazy.Lazy[*environment.Environment],
lazyEnvManger *lazy.Lazy[environment.Manager],
) azdext.ComposeServiceServer {
return &composeService{
lazyAzdContext: lazyAzdContext,
lazyEnv: lazyEnv,
lazyEnvManger: lazyEnvManger,
}
}

Expand All @@ -41,6 +48,16 @@ func (c *composeService) AddResource(
return nil, err
}

env, err := c.lazyEnv.GetValue()
if err != nil {
return nil, err
}

envManager, err := c.lazyEnvManger.GetValue()
if err != nil {
return nil, err
}

projectConfig, err := project.Load(ctx, azdContext.ProjectPath())
if err != nil {
return nil, err
Expand All @@ -55,11 +72,27 @@ func (c *composeService) AddResource(
return nil, fmt.Errorf("creating resource props: %w", err)
}

resourceId := req.Resource.ResourceId
projectConfig.Resources[req.Resource.Name] = &project.ResourceConfig{
Name: req.Resource.Name,
Type: project.ResourceType(req.Resource.Type),
Props: resourceProps,
Uses: req.Resource.Uses,
Name: req.Resource.Name,
Type: project.ResourceType(req.Resource.Type),
Props: resourceProps,
Uses: req.Resource.Uses,
ResourceId: resourceId,
}

if resourceId != "" {
// add existing:true to azure.yaml
if resource, exists := projectConfig.Resources[req.Resource.Name]; exists {
resource.Existing = true
}
// save resource id to env
env.DotenvSet(infra.ResourceIdName(req.Resource.Name), resourceId)

err = envManager.Save(ctx, env)
if err != nil {
return nil, fmt.Errorf("saving environment: %w", err)
}
}

if err := project.Save(ctx, projectConfig, azdContext.ProjectPath()); err != nil {
Expand Down Expand Up @@ -162,10 +195,11 @@ func (c *composeService) ListResources(
return nil, fmt.Errorf("marshaling resource config: %w", err)
}
composedResource := &azdext.ComposedResource{
Name: resource.Name,
Type: string(resource.Type),
Config: resourceConfigBytes,
Uses: resource.Uses,
Name: resource.Name,
Type: string(resource.Type),
Config: resourceConfigBytes,
Uses: resource.Uses,
ResourceId: resource.ResourceId,
}
composedResources = append(composedResources, composedResource)
}
Expand Down
52 changes: 47 additions & 5 deletions cli/azd/internal/grpcserver/compose_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
"testing"

"github.com/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
"github.com/azure/azure-dev/cli/azd/pkg/lazy"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockenv"
"github.com/stretchr/testify/require"
)

Expand All @@ -28,7 +30,15 @@ func Test_ComposeService_AddResource(t *testing.T) {
err := project.Save(*mockContext.Context, &projectConfig, azdCtx.ProjectPath())
require.NoError(t, err)
lazyAzdContext := lazy.From(azdCtx)
composeService := NewComposeService(lazyAzdContext)
env := environment.New("test")
envManager := &mockenv.MockEnvManager{}
lazyEnvManager := lazy.NewLazy(func() (environment.Manager, error) {
return envManager, nil
})
lazyEnv := lazy.NewLazy(func() (*environment.Environment, error) {
return env, nil
})
composeService := NewComposeService(lazyAzdContext, lazyEnv, lazyEnvManager)

t.Run("success", func(t *testing.T) {
addReq := &azdext.AddResourceRequest{
Expand Down Expand Up @@ -85,7 +95,15 @@ func Test_ComposeService_GetResource(t *testing.T) {
err := project.Save(*mockContext.Context, &projectConfig, azdCtx.ProjectPath())
require.NoError(t, err)
lazyAzdContext := lazy.From(azdCtx)
composeService := NewComposeService(lazyAzdContext)
env := environment.New("test")
envManager := &mockenv.MockEnvManager{}
lazyEnvManager := lazy.NewLazy(func() (environment.Manager, error) {
return envManager, nil
})
lazyEnv := lazy.NewLazy(func() (*environment.Environment, error) {
return env, nil
})
composeService := NewComposeService(lazyAzdContext, lazyEnv, lazyEnvManager)

t.Run("success", func(t *testing.T) {
getReq := &azdext.GetResourceRequest{
Expand Down Expand Up @@ -137,7 +155,15 @@ func Test_ComposeService_ListResources(t *testing.T) {
err := project.Save(*mockContext.Context, &projectConfig, azdCtx.ProjectPath())
require.NoError(t, err)
lazyAzdContext := lazy.From(azdCtx)
composeService := NewComposeService(lazyAzdContext)
env := environment.New("test")
envManager := &mockenv.MockEnvManager{}
lazyEnvManager := lazy.NewLazy(func() (environment.Manager, error) {
return envManager, nil
})
lazyEnv := lazy.NewLazy(func() (*environment.Environment, error) {
return env, nil
})
composeService := NewComposeService(lazyAzdContext, lazyEnv, lazyEnvManager)

listResp, err := composeService.ListResources(*mockContext.Context, &azdext.EmptyRequest{})
require.NoError(t, err)
Expand All @@ -156,7 +182,15 @@ func Test_ComposeService_ListResources(t *testing.T) {
lazyAzdContext := lazy.NewLazy(func() (*azdcontext.AzdContext, error) {
return nil, azdcontext.ErrNoProject
})
composeService := NewComposeService(lazyAzdContext)
env := environment.New("test")
envManager := &mockenv.MockEnvManager{}
lazyEnvManager := lazy.NewLazy(func() (environment.Manager, error) {
return envManager, nil
})
lazyEnv := lazy.NewLazy(func() (*environment.Environment, error) {
return env, nil
})
composeService := NewComposeService(lazyAzdContext, lazyEnv, lazyEnvManager)
_, err := composeService.ListResources(*mockContext.Context, &azdext.EmptyRequest{})
require.Error(t, err)
})
Expand All @@ -170,7 +204,15 @@ func Test_Test_ComposeService_ListResourceTypes(t *testing.T) {
})

// Create the service and call ListResourceTypes
service := NewComposeService(lazyAzdContext)
env := environment.New("test")
envManager := &mockenv.MockEnvManager{}
lazyEnvManager := lazy.NewLazy(func() (environment.Manager, error) {
return envManager, nil
})
lazyEnv := lazy.NewLazy(func() (*environment.Environment, error) {
return env, nil
})
service := NewComposeService(lazyAzdContext, lazyEnv, lazyEnvManager)
response, err := service.ListResourceTypes(*mockContext.Context, &azdext.EmptyRequest{})
require.NoError(t, err)
require.NotNil(t, response)
Expand Down
Loading
Loading