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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
17 changes: 17 additions & 0 deletions cli/azd/.vscode/cspell-azd-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ appinsightsexporter
appinsightsstorage
appplatform
appservice
aresource
arget
armapimanagement
armappconfiguration
Expand All @@ -41,6 +42,7 @@ azdcontext
azddeploy
azdev
azdexec
azdextb
azdinternal
azdtempl
azdtempl
Expand Down Expand Up @@ -97,20 +99,34 @@ dockerproject
doublestar
dskip
eastus
edisplay
eignore
eloading
Comment on lines +102 to +104
Copy link
Member

Choose a reason for hiding this comment

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

Are this used?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to add all of these new non-words?

Copy link
Contributor Author

@hemarina hemarina May 17, 2025

Choose a reason for hiding this comment

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

endregion
entra
entraid
envlist
envname
envsubst
eresource
errcheck
errorinfo
errorlint
eselect
eselected
esubscription
euser
eventhubs
eworkflow
executil
fcreating
fenable
flexconsumption
floading
frequired
Frontends
fsnotify
fsubscription
funcapp
functestapp
functionapp
Expand Down Expand Up @@ -179,6 +195,7 @@ pflag
pgadmin
posix
preinit
protogen
proxying
psanford
psycopg
Expand Down
34 changes: 20 additions & 14 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 @@ -343,10 +347,11 @@ func (a *startAction) Run(ctx context.Context, args []string) error {
}

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

serviceName := a.generateServiceName(appKey)
Expand Down Expand Up @@ -440,7 +445,8 @@ func (a *startAction) Run(ctx context.Context, args []string) error {
// Add any new resources to the azure.yaml.
for _, resource := range resourcesToAdd {
_, err := a.azdClient.Compose().AddResource(ctx, &azdext.AddResourceRequest{
Resource: resource,
Resource: resource,
ExistingId: resource.ResourceId,
})
if err != nil {
return fmt.Errorf("failed to add resource %s: %w", resource.Name, err)
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/grpc/proto/compose.proto
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ message GetResourceTypeResponse {
// AddResourceRequest is a request to add a new composability resource.
message AddResourceRequest {
ComposedResource resource = 1;
string ExistingId = 2;
}

// AddResourceResponse is the response of AddResource operation.
Expand All @@ -69,6 +70,7 @@ message ComposedResource {
string type = 2;
bytes config = 3;
repeated string uses = 4;
string resource_id = 5;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the ComposedResource already contains a ResourceId, do we really need to modify the AddResourceRequest ?

}

// ComposedResourceType represents a type of composability resource.
Expand Down
41 changes: 32 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]
env *environment.Environment
envManager environment.Manager
}

func NewComposeService(
lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext],
env *environment.Environment,
envManager environment.Manager,
) azdext.ComposeServiceServer {
return &composeService{
lazyAzdContext: lazyAzdContext,
env: env,
envManager: envManager,
}
}
Comment on lines 29 to 39
Copy link
Contributor

Choose a reason for hiding this comment

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

We have to be careful adding non-lazy dependencies to the gRPC server side services because the component initialization can fail. For example, try running the command in a fold that doesn't have an azd project or environment yet and this will likely fail.

Take a look at some of the other gRPC services for examples to use lazy components.


Expand Down Expand Up @@ -56,10 +63,25 @@ func (c *composeService) AddResource(
}

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: req.ExistingId,
}

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

err = c.envManager.Save(ctx, c.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 +184,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
22 changes: 17 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,9 @@ 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{}
composeService := NewComposeService(lazyAzdContext, env, envManager)

t.Run("success", func(t *testing.T) {
addReq := &azdext.AddResourceRequest{
Expand Down Expand Up @@ -85,7 +89,9 @@ 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{}
composeService := NewComposeService(lazyAzdContext, env, envManager)

t.Run("success", func(t *testing.T) {
getReq := &azdext.GetResourceRequest{
Expand Down Expand Up @@ -137,7 +143,9 @@ 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{}
composeService := NewComposeService(lazyAzdContext, env, envManager)

listResp, err := composeService.ListResources(*mockContext.Context, &azdext.EmptyRequest{})
require.NoError(t, err)
Expand All @@ -156,7 +164,9 @@ 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{}
composeService := NewComposeService(lazyAzdContext, env, envManager)
_, err := composeService.ListResources(*mockContext.Context, &azdext.EmptyRequest{})
require.Error(t, err)
})
Expand All @@ -170,7 +180,9 @@ func Test_Test_ComposeService_ListResourceTypes(t *testing.T) {
})

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