diff --git a/cli/azd/cmd/container.go b/cli/azd/cmd/container.go index 664b6448a41..983c9fbcaf0 100644 --- a/cli/azd/cmd/container.go +++ b/cli/azd/cmd/container.go @@ -825,6 +825,8 @@ func registerCommonDependencies(container *ioc.NestedContainer) { container.MustRegisterScoped(grpcserver.NewDeploymentService) container.MustRegisterScoped(grpcserver.NewEventService) container.MustRegisterSingleton(grpcserver.NewUserConfigService) + container.MustRegisterSingleton(grpcserver.NewComposeService) + container.MustRegisterSingleton(grpcserver.NewWorkflowService) // Required for nested actions called from composite actions like 'up' registerAction[*cmd.ProvisionAction](container, "azd-provision-action") diff --git a/cli/azd/docs/extension-framework.md b/cli/azd/docs/extension-framework.md index 9d527e93a3e..9fed6246d6d 100644 --- a/cli/azd/docs/extension-framework.md +++ b/cli/azd/docs/extension-framework.md @@ -11,6 +11,15 @@ Table of Contents - [Managing Extensions](#managing-extensions) - [Developing Extensions](#developing-extensions) - [Developer Artifacts](#developer-artifacts) +- [gRPC Services](#grpc-services) + - [Project Service](#project-service) + - [Environment Service](#environment-service) + - [User Config Service](#user-config-service) + - [Deployment Service](#deployment-service) + - [Prompt Service](#prompt-service) + - [Event Service](#event-service) + - [Compose Service](#compose-service) + - [Workflow Service](#workflow-service) ## Capabilities @@ -326,6 +335,8 @@ The following are a list of available gRPC services for extension developer to i - [Deployment Service](#deployment-service) - [Prompt Service](#prompt-service) - [Event Service](#event-service) +- [Compose Service](#compose-service) +- [Workflow Service](#workflow-service) ### Project Service @@ -345,9 +356,13 @@ Gets the current project configuration. - `services`: map of *ServiceConfig* - `infra`: *InfraOptions* -*See [project.proto](../grpc/proto/project.proto).* +#### AddService +Adds a new service to the project. ------------------------------------------- +- **Request:** *AddServiceRequest* + - Contains: + - `service`: *ServiceConfig* +- **Response:** *EmptyResponse* ### Environment Service @@ -467,8 +482,6 @@ Removes a config value at a given path. - `path` (string) - **Response:** *EmptyResponse* ------------------------------------------- - ### User Config Service This service manages user-specific configuration retrieval and updates. @@ -527,8 +540,6 @@ Removes a user configuration value. - Contains: - `status` (string) ------------------------------------------- - ### Deployment Service This service provides operations for deployment retrieval and context management. @@ -565,8 +576,6 @@ Retrieves the current deployment context. - `resourceGroup` (string) - `resources` (repeated string) ------------------------------------------- - ### Prompt Service This service manages user prompt interactions for subscriptions, locations, resources, and confirmations. @@ -738,3 +747,69 @@ Clients can subscribe to events and receive notifications via a bidirectional st - `service_name`: The name of the service. - `status`: Status such as "running", "completed", or "failed". - `message`: Optional additional details. + +### Compose Service + +This service manages composability resources in an AZD project. + +#### ListResources + +Lists all configured composability resources. + +- **Request:** *EmptyRequest* +- **Response:** *ListResourcesResponse* + - Contains a list of **ComposedResource** + +#### GetResource + +Retrieves the configuration of a specific composability resource. + +- **Request:** *GetResourceRequest* + - Contains: + - `name` (string) +- **Response:** *GetResourceResponse* + - Contains: + - `resource`: *ComposedResource* + +#### ListResourceTypes + +Lists all supported composability resource types. + +- **Request:** *EmptyRequest* +- **Response:** *ListResourceTypesResponse* + - Contains a list of **ComposedResourceType** + +#### GetResourceType + +Retrieves the schema of a specific composability resource type. + +- **Request:** *GetResourceTypeRequest* + - Contains: + - `type_name` (string) +- **Response:** *GetResourceTypeResponse* + - Contains: + - `resource_type`: *ComposedResourceType* + +#### AddResource + +Adds a new composability resource to the project. + +- **Request:** *AddResourceRequest* + - Contains: + - `resource`: *ComposedResource* +- **Response:** *AddResourceResponse* + - Contains: + - `resource`: *ComposedResource* + +### Workflow Service + +This service executes workflows defined within the project. + +#### Run + +Executes a workflow consisting of sequential steps. + +- **Request:** *RunWorkflowRequest* + - Contains: + - `workflow`: *Workflow* (with `name` and `steps`) +- **Response:** *EmptyResponse* diff --git a/cli/azd/grpc/proto/compose.proto b/cli/azd/grpc/proto/compose.proto index 48a46f0b23d..995101f36ad 100644 --- a/cli/azd/grpc/proto/compose.proto +++ b/cli/azd/grpc/proto/compose.proto @@ -68,6 +68,7 @@ message ComposedResource { string name = 1; string type = 2; bytes config = 3; + repeated string uses = 4; } // ComposedResourceType represents a type of composability resource. diff --git a/cli/azd/grpc/proto/project.proto b/cli/azd/grpc/proto/project.proto index ad32b9e46b9..a4d234cc22b 100644 --- a/cli/azd/grpc/proto/project.proto +++ b/cli/azd/grpc/proto/project.proto @@ -10,9 +10,17 @@ import "models.proto"; service ProjectService { // Gets the current project. rpc Get(EmptyRequest) returns (GetProjectResponse); + + // AddService adds a new service to the project. + rpc AddService(AddServiceRequest) returns (EmptyResponse); } // GetProjectResponse message definition message GetProjectResponse { ProjectConfig project = 1; } + +// AddServiceRequest message definition +message AddServiceRequest { + ServiceConfig service = 1; +} \ No newline at end of file diff --git a/cli/azd/grpc/proto/workflow.proto b/cli/azd/grpc/proto/workflow.proto new file mode 100644 index 00000000000..d00322bf5c6 --- /dev/null +++ b/cli/azd/grpc/proto/workflow.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +package azdext; + +option go_package = "github.com/azure/azure-dev/cli/azd/pkg/azdext;azdext"; + +import "models.proto"; + +service WorkflowService { + // ListResources retrieves all configured composability resources in the current project. + rpc Run(RunWorkflowRequest) returns (EmptyResponse); +} + +// RunWorkflowRequest is a request to run a workflow. +message RunWorkflowRequest { + Workflow workflow = 1; +} + +// Workflow is a collection of steps to be executed in order. +message Workflow { + string name = 1; + repeated WorkflowStep steps = 2; +} + +// WorkflowStep is a single step in a workflow. +message WorkflowStep { + WorkflowCommand command = 1; +} + +// WorkflowCommand is a command to be executed in a workflow step. +message WorkflowCommand { + repeated string args = 1; +} \ No newline at end of file diff --git a/cli/azd/internal/grpcserver/compose_service.go b/cli/azd/internal/grpcserver/compose_service.go new file mode 100644 index 00000000000..1e3a18dc57b --- /dev/null +++ b/cli/azd/internal/grpcserver/compose_service.go @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package grpcserver + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "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" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// composeService exposes features of the AZD composability model to the Extensions Framework layer. +type composeService struct { + azdext.UnimplementedComposeServiceServer + + lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext] +} + +func NewComposeService( + lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext], +) azdext.ComposeServiceServer { + return &composeService{ + lazyAzdContext: lazyAzdContext, + } +} + +// AddResource adds or updates a resource with the given name in the project configuration. +func (c *composeService) AddResource( + ctx context.Context, + req *azdext.AddResourceRequest, +) (*azdext.AddResourceResponse, error) { + azdContext, err := c.lazyAzdContext.GetValue() + if err != nil { + return nil, err + } + + projectConfig, err := project.Load(ctx, azdContext.ProjectPath()) + if err != nil { + return nil, err + } + + if projectConfig.Resources == nil { + projectConfig.Resources = make(map[string]*project.ResourceConfig) + } + + resourceProps, err := createResourceProps(req.Resource.Type, req.Resource.Config) + if err != nil { + return nil, fmt.Errorf("creating resource props: %w", err) + } + + projectConfig.Resources[req.Resource.Name] = &project.ResourceConfig{ + Name: req.Resource.Name, + Type: project.ResourceType(req.Resource.Type), + Props: resourceProps, + Uses: req.Resource.Uses, + } + + if err := project.Save(ctx, projectConfig, azdContext.ProjectPath()); err != nil { + return nil, err + } + + return &azdext.AddResourceResponse{ + Resource: req.Resource, + }, nil +} + +// GetResource retrieves a resource by its name from the project configuration. +// If the resource does not exist, it returns a NotFound error. +func (c *composeService) GetResource( + ctx context.Context, + req *azdext.GetResourceRequest, +) (*azdext.GetResourceResponse, error) { + azdContext, err := c.lazyAzdContext.GetValue() + if err != nil { + return nil, err + } + + projectConfig, err := project.Load(ctx, azdContext.ProjectPath()) + if err != nil { + return nil, err + } + + existingResource, has := projectConfig.Resources[req.Name] + if !has { + return nil, status.Errorf(codes.NotFound, "resource %s not found", req.Name) + } + + resourceConfigBytes, err := json.Marshal(existingResource.Props) + if err != nil { + return nil, fmt.Errorf("marshaling resource config: %w", err) + } + + composedResource := &azdext.ComposedResource{ + Name: existingResource.Name, + Type: string(existingResource.Type), + Config: resourceConfigBytes, + Uses: existingResource.Uses, + } + + return &azdext.GetResourceResponse{ + Resource: composedResource, + }, nil +} + +// GetResourceType gets the resource type configuration schema by the specified name. +func (c *composeService) GetResourceType( + context.Context, + *azdext.GetResourceTypeRequest, +) (*azdext.GetResourceTypeResponse, error) { + panic("unimplemented") +} + +// ListResourceTypes lists all available resource types. +func (c *composeService) ListResourceTypes( + context.Context, + *azdext.EmptyRequest, +) (*azdext.ListResourceTypesResponse, error) { + panic("unimplemented") +} + +// ListResources lists all resources in the project configuration. +func (c *composeService) ListResources( + ctx context.Context, + req *azdext.EmptyRequest, +) (*azdext.ListResourcesResponse, error) { + azdContext, err := c.lazyAzdContext.GetValue() + if err != nil { + return nil, err + } + + projectConfig, err := project.Load(ctx, azdContext.ProjectPath()) + if err != nil { + return nil, err + } + + existingResources := projectConfig.Resources + composedResources := make([]*azdext.ComposedResource, 0, len(existingResources)) + + for _, resource := range existingResources { + resourceConfigBytes, err := json.Marshal(resource.Props) + if err != nil { + return nil, fmt.Errorf("marshaling resource config: %w", err) + } + composedResource := &azdext.ComposedResource{ + Name: resource.Name, + Type: string(resource.Type), + Config: resourceConfigBytes, + Uses: resource.Uses, + } + composedResources = append(composedResources, composedResource) + } + + return &azdext.ListResourcesResponse{ + Resources: composedResources, + }, nil +} + +// createResourceProps unmarshals the resource configuration bytes into the appropriate struct based on the resource type. +// For the short term this marshalling of resource properties needs to stay in sync with `pkg\project\resources.go` +// In the future we will converge this into a common component. +func createResourceProps(resourceType string, config []byte) (any, error) { + switch project.ResourceType(resourceType) { + case project.ResourceTypeHostContainerApp: + props := project.ContainerAppProps{} + if len(config) == 0 { + return props, nil + } + if err := json.Unmarshal(config, &props); err != nil { + return nil, err + } + return props, nil + case project.ResourceTypeDbCosmos: + props := project.CosmosDBProps{} + if len(config) == 0 { + return props, nil + } + if err := json.Unmarshal(config, &props); err != nil { + return nil, err + } + return props, nil + case project.ResourceTypeStorage: + props := project.StorageProps{} + if len(config) == 0 { + return props, nil + } + if err := json.Unmarshal(config, &props); err != nil { + return nil, err + } + return props, nil + case project.ResourceTypeAiProject: + props := project.AiFoundryModelProps{} + if len(config) == 0 { + return props, nil + } + if err := json.Unmarshal(config, &props); err != nil { + return nil, err + } + return props, nil + case project.ResourceTypeDbMongo: + props := project.CosmosDBProps{} + if len(config) == 0 { + return props, nil + } + if err := json.Unmarshal(config, &props); err != nil { + return nil, err + } + return props, nil + case project.ResourceTypeMessagingEventHubs: + props := project.EventHubsProps{} + if len(config) == 0 { + return props, nil + } + if err := json.Unmarshal(config, &props); err != nil { + return nil, err + } + return props, nil + case project.ResourceTypeMessagingServiceBus: + props := project.ServiceBusProps{} + if len(config) == 0 { + return props, nil + } + if err := json.Unmarshal(config, &props); err != nil { + return nil, err + } + return props, nil + default: + return nil, nil + } +} diff --git a/cli/azd/internal/grpcserver/compose_service_test.go b/cli/azd/internal/grpcserver/compose_service_test.go new file mode 100644 index 00000000000..b7c3aac7fca --- /dev/null +++ b/cli/azd/internal/grpcserver/compose_service_test.go @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package grpcserver + +import ( + "context" + "encoding/json" + "testing" + + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "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/stretchr/testify/require" +) + +func Test_ComposeService_AddResource(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + temp := t.TempDir() + azdCtx := azdcontext.NewAzdContextWithDirectory(temp) + projectConfig := project.ProjectConfig{ + Name: "test", + } + err := project.Save(*mockContext.Context, &projectConfig, azdCtx.ProjectPath()) + require.NoError(t, err) + lazyAzdContext := lazy.From(azdCtx) + composeService := NewComposeService(lazyAzdContext) + + t.Run("success", func(t *testing.T) { + addReq := &azdext.AddResourceRequest{ + Resource: &azdext.ComposedResource{ + Name: "resource1", + Type: "Storage", + Config: []byte("{}"), + Uses: []string{}, + }, + } + addResp, err := composeService.AddResource(*mockContext.Context, addReq) + require.NoError(t, err) + require.NotNil(t, addResp) + require.Equal(t, addReq.Resource.Name, addResp.Resource.Name) + + updatedConfig, err := project.Load(*mockContext.Context, azdCtx.ProjectPath()) + require.NoError(t, err) + res, exists := updatedConfig.Resources["resource1"] + require.True(t, exists) + require.Equal(t, "resource1", res.Name) + require.Equal(t, project.ResourceType("Storage"), res.Type) + }) + + t.Run("invalid config", func(t *testing.T) { + // reuse the same common setup. + addReq := &azdext.AddResourceRequest{ + Resource: &azdext.ComposedResource{ + Name: "invalid", + Type: "storage", + Config: []byte("invalid json"), + Uses: []string{}, + }, + } + _, err := composeService.AddResource(*mockContext.Context, addReq) + require.Error(t, err) + }) +} + +func Test_ComposeService_GetResource(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + temp := t.TempDir() + azdCtx := azdcontext.NewAzdContextWithDirectory(temp) + projectConfig := project.ProjectConfig{ + Name: "test", + Resources: map[string]*project.ResourceConfig{ + "resource1": { + Name: "resource1", + Type: project.ResourceTypeStorage, + Props: project.StorageProps{}, + Uses: []string{}, + }, + }, + } + err := project.Save(*mockContext.Context, &projectConfig, azdCtx.ProjectPath()) + require.NoError(t, err) + lazyAzdContext := lazy.From(azdCtx) + composeService := NewComposeService(lazyAzdContext) + + t.Run("success", func(t *testing.T) { + getReq := &azdext.GetResourceRequest{ + Name: "resource1", + } + getResp, err := composeService.GetResource(*mockContext.Context, getReq) + require.NoError(t, err) + require.NotNil(t, getResp) + require.Equal(t, "resource1", getResp.Resource.Name) + require.Equal(t, "storage", getResp.Resource.Type) + + configBytes, err := json.Marshal(project.StorageProps{}) + require.NoError(t, err) + require.JSONEq(t, string(configBytes), string(getResp.Resource.Config)) + }) + + t.Run("resource not found", func(t *testing.T) { + getReq := &azdext.GetResourceRequest{ + Name: "nonexistent", + } + _, err = composeService.GetResource(*mockContext.Context, getReq) + require.Error(t, err) + }) +} + +func Test_ComposeService_ListResources(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + + t.Run("success", func(t *testing.T) { + temp := t.TempDir() + azdCtx := azdcontext.NewAzdContextWithDirectory(temp) + projectConfig := project.ProjectConfig{ + Name: "test", + Resources: map[string]*project.ResourceConfig{ + "resource1": { + Name: "resource1", + Type: project.ResourceTypeStorage, + Props: project.StorageProps{}, + Uses: []string{}, + }, + "resource2": { + Name: "resource2", + Type: project.ResourceTypeDbCosmos, + Props: project.CosmosDBProps{}, + Uses: []string{"resource1"}, + }, + }, + } + err := project.Save(*mockContext.Context, &projectConfig, azdCtx.ProjectPath()) + require.NoError(t, err) + lazyAzdContext := lazy.From(azdCtx) + composeService := NewComposeService(lazyAzdContext) + + listResp, err := composeService.ListResources(*mockContext.Context, &azdext.EmptyRequest{}) + require.NoError(t, err) + require.NotNil(t, listResp) + require.Len(t, listResp.Resources, 2) + names := map[string]bool{} + for _, res := range listResp.Resources { + names[res.Name] = true + } + require.True(t, names["resource1"]) + require.True(t, names["resource2"]) + }) + + t.Run("no project", func(t *testing.T) { + // For this subtest, simulate no project using a lazy context + lazyAzdContext := lazy.NewLazy(func() (*azdcontext.AzdContext, error) { + return nil, azdcontext.ErrNoProject + }) + composeService := NewComposeService(lazyAzdContext) + _, err := composeService.ListResources(*mockContext.Context, &azdext.EmptyRequest{}) + require.Error(t, err) + }) +} diff --git a/cli/azd/internal/grpcserver/environment_service.go b/cli/azd/internal/grpcserver/environment_service.go index 8b215ca392d..ad0b684d00f 100644 --- a/cli/azd/internal/grpcserver/environment_service.go +++ b/cli/azd/internal/grpcserver/environment_service.go @@ -18,10 +18,6 @@ type environmentService struct { azdext.UnimplementedEnvironmentServiceServer lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext] lazyEnvManager *lazy.Lazy[environment.Manager] - - azdContext *azdcontext.AzdContext - envManager environment.Manager - initialized bool } func NewEnvironmentService( @@ -35,11 +31,12 @@ func NewEnvironmentService( } func (s *environmentService) List(ctx context.Context, req *azdext.EmptyRequest) (*azdext.EnvironmentListResponse, error) { - if err := s.initialize(); err != nil { + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { return nil, err } - envList, err := s.envManager.List(ctx) + envList, err := envManager.List(ctx) if err != nil { return nil, err } @@ -63,10 +60,6 @@ func (s *environmentService) GetCurrent( ctx context.Context, req *azdext.EmptyRequest, ) (*azdext.EnvironmentResponse, error) { - if err := s.initialize(); err != nil { - return nil, err - } - env, err := s.currentEnvironment(ctx) if err != nil { return nil, err @@ -83,11 +76,12 @@ func (s *environmentService) Get( ctx context.Context, req *azdext.GetEnvironmentRequest, ) (*azdext.EnvironmentResponse, error) { - if err := s.initialize(); err != nil { + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { return nil, err } - env, err := s.envManager.Get(ctx, req.Name) + env, err := envManager.Get(ctx, req.Name) if err != nil { return nil, err } @@ -103,11 +97,17 @@ func (s *environmentService) Select( ctx context.Context, req *azdext.SelectEnvironmentRequest, ) (*azdext.EmptyResponse, error) { - if err := s.initialize(); err != nil { + azdContext, err := s.lazyAzdContext.GetValue() + if err != nil { + return nil, err + } + + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { return nil, err } - env, err := s.envManager.Get(ctx, req.Name) + env, err := envManager.Get(ctx, req.Name) if err != nil { return nil, err } @@ -116,7 +116,7 @@ func (s *environmentService) Select( DefaultEnvironment: env.Name(), } - if err := s.azdContext.SetProjectState(projectState); err != nil { + if err := azdContext.SetProjectState(projectState); err != nil { return nil, err } @@ -128,11 +128,12 @@ func (s *environmentService) GetValues( ctx context.Context, req *azdext.GetEnvironmentRequest, ) (*azdext.KeyValueListResponse, error) { - if err := s.initialize(); err != nil { + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { return nil, err } - env, err := s.envManager.Get(ctx, req.Name) + env, err := envManager.Get(ctx, req.Name) if err != nil { return nil, err } @@ -156,11 +157,12 @@ func (s *environmentService) GetValues( // GetValue retrieves the value of a specific key in the specified environment. func (s *environmentService) GetValue(ctx context.Context, req *azdext.GetEnvRequest) (*azdext.KeyValueResponse, error) { - if err := s.initialize(); err != nil { + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { return nil, err } - env, err := s.envManager.Get(ctx, req.EnvName) + env, err := envManager.Get(ctx, req.EnvName) if err != nil { return nil, err } @@ -175,26 +177,36 @@ func (s *environmentService) GetValue(ctx context.Context, req *azdext.GetEnvReq // SetValue sets the value of a key in the specified environment. func (s *environmentService) SetValue(ctx context.Context, req *azdext.SetEnvRequest) (*azdext.EmptyResponse, error) { - if err := s.initialize(); err != nil { + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { return nil, err } - env, err := s.envManager.Get(ctx, req.EnvName) + env, err := envManager.Get(ctx, req.EnvName) if err != nil { return nil, err } env.DotenvSet(req.Key, req.Value) + if err := envManager.Save(ctx, env); err != nil { + return nil, fmt.Errorf("failed to save environment: %w", err) + } return &azdext.EmptyResponse{}, nil } func (s *environmentService) currentEnvironment(ctx context.Context) (*environment.Environment, error) { - if err := s.initialize(); err != nil { + azdContext, err := s.lazyAzdContext.GetValue() + if err != nil { return nil, err } - defaultEnvironment, err := s.azdContext.GetDefaultEnvironmentName() + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { + return nil, err + } + + defaultEnvironment, err := azdContext.GetDefaultEnvironmentName() if err != nil { return nil, err } @@ -203,7 +215,7 @@ func (s *environmentService) currentEnvironment(ctx context.Context) (*environme return nil, environment.ErrDefaultEnvironmentNotFound } - env, err := s.envManager.Get(ctx, defaultEnvironment) + env, err := envManager.Get(ctx, defaultEnvironment) if err != nil { return nil, err } @@ -216,10 +228,6 @@ func (s *environmentService) GetConfig( ctx context.Context, req *azdext.GetConfigRequest, ) (*azdext.GetConfigResponse, error) { - if err := s.initialize(); err != nil { - return nil, err - } - env, err := s.currentEnvironment(ctx) if err != nil { return nil, err @@ -248,10 +256,6 @@ func (s *environmentService) GetConfigString( ctx context.Context, req *azdext.GetConfigStringRequest, ) (*azdext.GetConfigStringResponse, error) { - if err := s.initialize(); err != nil { - return nil, err - } - env, err := s.currentEnvironment(ctx) if err != nil { return nil, err @@ -270,10 +274,6 @@ func (s *environmentService) GetConfigSection( ctx context.Context, req *azdext.GetConfigSectionRequest, ) (*azdext.GetConfigSectionResponse, error) { - if err := s.initialize(); err != nil { - return nil, err - } - env, err := s.currentEnvironment(ctx) if err != nil { return nil, err @@ -304,7 +304,8 @@ func (s *environmentService) GetConfigSection( // SetConfig sets a config value at a given path. func (s *environmentService) SetConfig(ctx context.Context, req *azdext.SetConfigRequest) (*azdext.EmptyResponse, error) { - if err := s.initialize(); err != nil { + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { return nil, err } @@ -322,7 +323,7 @@ func (s *environmentService) SetConfig(ctx context.Context, req *azdext.SetConfi return nil, fmt.Errorf("failed to set value: %w", err) } - if err := s.envManager.Save(ctx, env); err != nil { + if err := envManager.Save(ctx, env); err != nil { return nil, fmt.Errorf("failed to save config: %w", err) } @@ -334,7 +335,8 @@ func (s *environmentService) UnsetConfig( ctx context.Context, req *azdext.UnsetConfigRequest, ) (*azdext.EmptyResponse, error) { - if err := s.initialize(); err != nil { + envManager, err := s.lazyEnvManager.GetValue() + if err != nil { return nil, err } @@ -347,31 +349,9 @@ func (s *environmentService) UnsetConfig( return nil, fmt.Errorf("failed to unset value: %w", err) } - if err := s.envManager.Save(ctx, env); err != nil { + if err := envManager.Save(ctx, env); err != nil { return nil, fmt.Errorf("failed to save config: %w", err) } return &azdext.EmptyResponse{}, nil } - -func (s *environmentService) initialize() error { - if s.initialized { - return nil - } - - azdContext, err := s.lazyAzdContext.GetValue() - if err != nil { - return err - } - - envManager, err := s.lazyEnvManager.GetValue() - if err != nil { - return err - } - - s.azdContext = azdContext - s.envManager = envManager - s.initialized = true - - return nil -} diff --git a/cli/azd/internal/grpcserver/project_service.go b/cli/azd/internal/grpcserver/project_service.go index f0f98d0a107..a276d89265d 100644 --- a/cli/azd/internal/grpcserver/project_service.go +++ b/cli/azd/internal/grpcserver/project_service.go @@ -18,11 +18,6 @@ type projectService struct { lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext] lazyEnvManager *lazy.Lazy[environment.Manager] - - azdContext *azdcontext.AzdContext - envManager environment.Manager - - initialized bool } func NewProjectService( @@ -36,11 +31,12 @@ func NewProjectService( } func (s *projectService) Get(ctx context.Context, req *azdext.EmptyRequest) (*azdext.GetProjectResponse, error) { - if err := s.initialize(); err != nil { + azdContext, err := s.lazyAzdContext.GetValue() + if err != nil { return nil, err } - projectConfig, err := project.Load(ctx, s.azdContext.ProjectPath()) + projectConfig, err := project.Load(ctx, azdContext.ProjectPath()) if err != nil { return nil, err } @@ -49,13 +45,18 @@ func (s *projectService) Get(ctx context.Context, req *azdext.EmptyRequest) (*az return "" } - defaultEnvironment, err := s.azdContext.GetDefaultEnvironmentName() + defaultEnvironment, err := azdContext.GetDefaultEnvironmentName() + if err != nil { + return nil, err + } + + envManager, err := s.lazyEnvManager.GetValue() if err != nil { return nil, err } if defaultEnvironment != "" { - env, err := s.envManager.Get(ctx, defaultEnvironment) + env, err := envManager.Get(ctx, defaultEnvironment) if err == nil && env != nil { envKeyMapper = env.Getenv } @@ -98,24 +99,33 @@ func (s *projectService) Get(ctx context.Context, req *azdext.EmptyRequest) (*az }, nil } -func (s *projectService) initialize() error { - if s.initialized { - return nil - } - +func (s *projectService) AddService(ctx context.Context, req *azdext.AddServiceRequest) (*azdext.EmptyResponse, error) { azdContext, err := s.lazyAzdContext.GetValue() if err != nil { - return err + return nil, err } - envManager, err := s.lazyEnvManager.GetValue() + projectConfig, err := project.Load(ctx, azdContext.ProjectPath()) if err != nil { - return err + return nil, err + } + + serviceConfig := &project.ServiceConfig{ + Project: projectConfig, + Name: req.Service.Name, + RelativePath: req.Service.RelativePath, + Language: project.ServiceLanguageKind(req.Service.Language), + Host: project.ServiceTargetKind(req.Service.Host), + } + + if projectConfig.Services == nil { + projectConfig.Services = map[string]*project.ServiceConfig{} } - s.azdContext = azdContext - s.envManager = envManager - s.initialized = true + projectConfig.Services[req.Service.Name] = serviceConfig + if err := project.Save(ctx, projectConfig, azdContext.ProjectPath()); err != nil { + return nil, err + } - return nil + return &azdext.EmptyResponse{}, nil } diff --git a/cli/azd/internal/grpcserver/project_service_test.go b/cli/azd/internal/grpcserver/project_service_test.go index 4ec2663a2bf..ff55a66ef13 100644 --- a/cli/azd/internal/grpcserver/project_service_test.go +++ b/cli/azd/internal/grpcserver/project_service_test.go @@ -5,6 +5,7 @@ package grpcserver import ( "context" + "path/filepath" "testing" "github.com/azure/azure-dev/cli/azd/pkg/azdext" @@ -84,3 +85,59 @@ func Test_ProjectService_Flow(t *testing.T) { require.NotNil(t, getResponse) require.Equal(t, projectConfig.Name, getResponse.Project.Name) } + +func Test_ProjectService_AddService(t *testing.T) { + // Setup a mock context and temporary project directory. + mockContext := mocks.NewMockContext(context.Background()) + temp := t.TempDir() + + // Initialize AzdContext with the temporary directory. + azdContext := azdcontext.NewAzdContextWithDirectory(temp) + + // Define and save project configuration. + projectConfig := project.ProjectConfig{ + Name: "test", + } + err := project.Save(*mockContext.Context, &projectConfig, azdContext.ProjectPath()) + require.NoError(t, err) + + // Configure and initialize environment manager. + fileConfigManager := config.NewFileConfigManager(config.NewManager()) + localDataStore := environment.NewLocalFileDataStore(azdContext, fileConfigManager) + envManager, err := environment.NewManager(mockContext.Container, azdContext, mockContext.Console, localDataStore, nil) + require.NoError(t, err) + require.NotNil(t, envManager) + + // Create lazy-loaded instances. + lazyAzdContext := lazy.From(azdContext) + lazyEnvManager := lazy.From(envManager) + + // Create the project service. + service := NewProjectService(lazyAzdContext, lazyEnvManager) + + // Prepare a new service addition request. + serviceRequest := &azdext.AddServiceRequest{ + Service: &azdext.ServiceConfig{ + Name: "service1", + RelativePath: filepath.Join("src", "service1"), + Language: "python", + Host: "containerapp", + }, + } + + // Call AddService. + emptyResponse, err := service.AddService(*mockContext.Context, serviceRequest) + require.NoError(t, err) + require.NotNil(t, emptyResponse) + + // Reload the project configuration and verify the service was added. + updatedConfig, err := project.Load(*mockContext.Context, azdContext.ProjectPath()) + require.NoError(t, err) + require.NotNil(t, updatedConfig.Services) + serviceConfig, exists := updatedConfig.Services["service1"] + require.True(t, exists) + require.Equal(t, "service1", serviceConfig.Name) + require.Equal(t, filepath.Join("src", "service1"), serviceConfig.RelativePath) + require.Equal(t, project.ServiceLanguagePython, serviceConfig.Language) + require.Equal(t, project.ContainerAppTarget, serviceConfig.Host) +} diff --git a/cli/azd/internal/grpcserver/prompt_service.go b/cli/azd/internal/grpcserver/prompt_service.go index 048056bd0d5..09087c6fbb1 100644 --- a/cli/azd/internal/grpcserver/prompt_service.go +++ b/cli/azd/internal/grpcserver/prompt_service.go @@ -297,7 +297,6 @@ func createResourceOptions(options *azdext.PromptResourceOptions) prompt.Resourc selectOptions = &prompt.SelectOptions{ ForceNewResource: options.SelectOptions.ForceNewResource, NewResourceMessage: options.SelectOptions.NewResourceMessage, - CreatingMessage: options.SelectOptions.CreatingMessage, Message: options.SelectOptions.Message, HelpMessage: options.SelectOptions.HelpMessage, LoadingMessage: options.SelectOptions.LoadingMessage, diff --git a/cli/azd/internal/grpcserver/server.go b/cli/azd/internal/grpcserver/server.go index febdfaa363f..0ea0f9f1b4e 100644 --- a/cli/azd/internal/grpcserver/server.go +++ b/cli/azd/internal/grpcserver/server.go @@ -31,6 +31,8 @@ type Server struct { userConfigService azdext.UserConfigServiceServer deploymentService azdext.DeploymentServiceServer eventService azdext.EventServiceServer + composeService azdext.ComposeServiceServer + workflowService azdext.WorkflowServiceServer } func NewServer( @@ -40,6 +42,8 @@ func NewServer( userConfigService azdext.UserConfigServiceServer, deploymentService azdext.DeploymentServiceServer, eventService azdext.EventServiceServer, + composeService azdext.ComposeServiceServer, + workflowService azdext.WorkflowServiceServer, ) *Server { return &Server{ projectService: projectService, @@ -48,6 +52,8 @@ func NewServer( userConfigService: userConfigService, deploymentService: deploymentService, eventService: eventService, + composeService: composeService, + workflowService: workflowService, } } @@ -72,13 +78,15 @@ func (s *Server) Start() (*ServerInfo, error) { // Get the assigned random port randomPort := listener.Addr().(*net.TCPAddr).Port - // Register the Greeter service with the gRPC server + // Register the azd services with the gRPC server azdext.RegisterProjectServiceServer(s.grpcServer, s.projectService) azdext.RegisterEnvironmentServiceServer(s.grpcServer, s.environmentService) azdext.RegisterPromptServiceServer(s.grpcServer, s.promptService) azdext.RegisterUserConfigServiceServer(s.grpcServer, s.userConfigService) azdext.RegisterDeploymentServiceServer(s.grpcServer, s.deploymentService) azdext.RegisterEventServiceServer(s.grpcServer, s.eventService) + azdext.RegisterComposeServiceServer(s.grpcServer, s.composeService) + azdext.RegisterWorkflowServiceServer(s.grpcServer, s.workflowService) serverInfo.Address = fmt.Sprintf("localhost:%d", randomPort) serverInfo.Port = randomPort diff --git a/cli/azd/internal/grpcserver/server_test.go b/cli/azd/internal/grpcserver/server_test.go index b9380ed0e0f..8f0296661b3 100644 --- a/cli/azd/internal/grpcserver/server_test.go +++ b/cli/azd/internal/grpcserver/server_test.go @@ -28,6 +28,8 @@ func Test_Server_Start(t *testing.T) { azdext.UnimplementedUserConfigServiceServer{}, azdext.UnimplementedDeploymentServiceServer{}, azdext.UnimplementedEventServiceServer{}, + azdext.UnimplementedComposeServiceServer{}, + azdext.UnimplementedWorkflowServiceServer{}, ) serverInfo, err := server.Start() diff --git a/cli/azd/internal/grpcserver/workflow_service.go b/cli/azd/internal/grpcserver/workflow_service.go new file mode 100644 index 00000000000..1092fa44b86 --- /dev/null +++ b/cli/azd/internal/grpcserver/workflow_service.go @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package grpcserver + +import ( + "context" + + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "github.com/azure/azure-dev/cli/azd/pkg/workflow" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// workflowService exposes features of AZD workflows to the Extensions Framework layer. +type workflowService struct { + azdext.UnimplementedWorkflowServiceServer + + runner *workflow.Runner +} + +// NewWorkflowService creates a new instance of the workflow service. +func NewWorkflowService(runner *workflow.Runner) azdext.WorkflowServiceServer { + return &workflowService{ + runner: runner, + } +} + +// Run executes the specified workflow. +func (s *workflowService) Run(ctx context.Context, request *azdext.RunWorkflowRequest) (*azdext.EmptyResponse, error) { + wf := request.Workflow + if wf == nil || len(wf.Steps) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "workflow is empty") + } + + azdWorkflow, err := convertWorkflow(wf) + if err != nil { + return nil, err + } + + if err := s.runner.Run(ctx, azdWorkflow); err != nil { + return nil, status.Errorf(codes.Internal, "failed to run workflow: %v", err) + } + + return &azdext.EmptyResponse{}, nil +} + +// convertWorkflow converts an azdext.Workflow to a workflow.Workflow. +func convertWorkflow(wf *azdext.Workflow) (*workflow.Workflow, error) { + azdWorkflow := workflow.Workflow{ + Name: wf.Name, + Steps: []*workflow.Step{}, + } + + for _, step := range wf.Steps { + if step.Command == nil || len(step.Command.Args) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "step command is empty") + } + + azdStep := &workflow.Step{ + AzdCommand: workflow.Command{ + Args: step.Command.Args, + }, + } + + azdWorkflow.Steps = append(azdWorkflow.Steps, azdStep) + } + + return &azdWorkflow, nil +} diff --git a/cli/azd/internal/grpcserver/workflow_service_test.go b/cli/azd/internal/grpcserver/workflow_service_test.go new file mode 100644 index 00000000000..8fcaaab69a5 --- /dev/null +++ b/cli/azd/internal/grpcserver/workflow_service_test.go @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package grpcserver + +import ( + "context" + "errors" + "testing" + + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "github.com/azure/azure-dev/cli/azd/pkg/workflow" + "github.com/azure/azure-dev/cli/azd/test/mocks" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func Test_WorkflowService_Run_Success(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + + t.Run("Success", func(t *testing.T) { + testRunner := &TestWorkflowRunner{} + runner := workflow.NewRunner(testRunner, mockContext.Console) + testRunner.On("SetArgs", mock.Anything) + testRunner.On("ExecuteContext", *mockContext.Context).Return(nil) + + service := NewWorkflowService(runner) + + // Create a valid, non-empty workflow. + req := &azdext.RunWorkflowRequest{ + Workflow: &azdext.Workflow{ + Name: "testWorkflow", + Steps: []*azdext.WorkflowStep{ + { + Command: &azdext.WorkflowCommand{ + Args: []string{"provision"}, + }, + }, + }, + }, + } + + // Act + resp, err := service.Run(*mockContext.Context, req) + + // Assert + require.NoError(t, err) + require.NotNil(t, resp) + + // Verify that the runner's Run method was invoked. + testRunner.AssertCalled(t, "SetArgs", []string{"provision"}) + testRunner.AssertCalled(t, "ExecuteContext", *mockContext.Context) + }) + + t.Run("Failure", func(t *testing.T) { + expectedErr := errors.New("execution failed") + testRunner := &TestWorkflowRunner{} + runner := workflow.NewRunner(testRunner, mockContext.Console) + testRunner.On("SetArgs", mock.Anything) + testRunner.On("ExecuteContext", *mockContext.Context).Return(expectedErr) + + service := NewWorkflowService(runner) + + // Create a valid, non-empty workflow. + req := &azdext.RunWorkflowRequest{ + Workflow: &azdext.Workflow{ + Name: "testWorkflow", + Steps: []*azdext.WorkflowStep{ + { + Command: &azdext.WorkflowCommand{ + Args: []string{"provision"}, + }, + }, + }, + }, + } + + // Act + resp, err := service.Run(*mockContext.Context, req) + + // Assert + require.Error(t, err) + require.Nil(t, resp) + + // Verify that the runner's Run method was invoked. + testRunner.AssertCalled(t, "SetArgs", []string{"provision"}) + testRunner.AssertCalled(t, "ExecuteContext", *mockContext.Context) + }) +} + +// Updated TestWorkflowRunner using testify/mock. +type TestWorkflowRunner struct { + mock.Mock +} + +// Modified SetArgs to use testify/mock. +func (r *TestWorkflowRunner) SetArgs(args []string) { + r.Called(args) +} + +// Modified ExecuteContext to use testify/mock. +func (r *TestWorkflowRunner) ExecuteContext(ctx context.Context) error { + ret := r.Called(ctx) + return ret.Error(0) +} diff --git a/cli/azd/pkg/azdext/azd_client.go b/cli/azd/pkg/azdext/azd_client.go index 576f36ad27d..50787403004 100644 --- a/cli/azd/pkg/azdext/azd_client.go +++ b/cli/azd/pkg/azdext/azd_client.go @@ -24,6 +24,8 @@ type AzdClient struct { promptClient PromptServiceClient deploymentClient DeploymentServiceClient eventsClient EventServiceClient + composeClient ComposeServiceClient + workflowClient WorkflowServiceClient } // WithAddress sets the address of the `azd` gRPC server. @@ -125,3 +127,21 @@ func (c *AzdClient) Events() EventServiceClient { return c.eventsClient } + +// Compose returns the compose service client. +func (c *AzdClient) Compose() ComposeServiceClient { + if c.composeClient == nil { + c.composeClient = NewComposeServiceClient(c.connection) + } + + return c.composeClient +} + +// Workflow returns the workflow service client. +func (c *AzdClient) Workflow() WorkflowServiceClient { + if c.workflowClient == nil { + c.workflowClient = NewWorkflowServiceClient(c.connection) + } + + return c.workflowClient +} diff --git a/cli/azd/pkg/azdext/compose.pb.go b/cli/azd/pkg/azdext/compose.pb.go index 28a8a0b8296..7b12f95d724 100644 --- a/cli/azd/pkg/azdext/compose.pb.go +++ b/cli/azd/pkg/azdext/compose.pb.go @@ -397,9 +397,10 @@ type ComposedResource struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` - Config []byte `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Config []byte `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"` + Uses []string `protobuf:"bytes,4,rep,name=uses,proto3" json:"uses,omitempty"` } func (x *ComposedResource) Reset() { @@ -453,6 +454,13 @@ func (x *ComposedResource) GetConfig() []byte { return nil } +func (x *ComposedResource) GetUses() []string { + if x != nil { + return x.Uses + } + return nil +} + // ComposedResourceType represents a type of composability resource. type ComposedResourceType struct { state protoimpl.MessageState @@ -549,46 +557,47 @@ var file_compose_proto_rawDesc = []byte{ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x52, 0x0a, + 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x66, 0x0a, 0x10, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x22, 0x4b, 0x0a, 0x14, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, - 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x32, 0x88, - 0x03, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, - 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x7a, 0x64, - 0x65, 0x78, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, - 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x1e, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x1a, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, - 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x2f, 0x61, 0x7a, - 0x75, 0x72, 0x65, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x61, 0x7a, 0x64, 0x2f, - 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x3b, 0x61, 0x7a, 0x64, 0x65, 0x78, - 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x04, 0x75, 0x73, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x14, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x32, 0x88, 0x03, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, + 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x2e, 0x61, 0x7a, 0x64, + 0x65, 0x78, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, + 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, + 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x64, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1b, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, + 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x7a, 0x75, 0x72, + 0x65, 0x2f, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x6c, 0x69, 0x2f, + 0x61, 0x7a, 0x64, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x3b, 0x61, + 0x7a, 0x64, 0x65, 0x78, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/cli/azd/pkg/azdext/project.pb.go b/cli/azd/pkg/azdext/project.pb.go index 5b8418f532d..af787dd3027 100644 --- a/cli/azd/pkg/azdext/project.pb.go +++ b/cli/azd/pkg/azdext/project.pb.go @@ -69,6 +69,51 @@ func (x *GetProjectResponse) GetProject() *ProjectConfig { return nil } +type AddServiceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Service *ServiceConfig `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` +} + +func (x *AddServiceRequest) Reset() { + *x = AddServiceRequest{} + mi := &file_project_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddServiceRequest) ProtoMessage() {} + +func (x *AddServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_project_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddServiceRequest.ProtoReflect.Descriptor instead. +func (*AddServiceRequest) Descriptor() ([]byte, []int) { + return file_project_proto_rawDescGZIP(), []int{1} +} + +func (x *AddServiceRequest) GetService() *ServiceConfig { + if x != nil { + return x.Service + } + return nil +} + var File_project_proto protoreflect.FileDescriptor var file_project_proto_rawDesc = []byte{ @@ -78,16 +123,24 @@ var file_project_proto_rawDesc = []byte{ 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x32, 0x49, 0x0a, 0x0e, - 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, - 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x7a, - 0x64, 0x65, 0x78, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x2f, 0x61, 0x7a, 0x75, 0x72, - 0x65, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x61, 0x7a, 0x64, 0x2f, 0x70, 0x6b, - 0x67, 0x2f, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x3b, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x66, 0x69, 0x67, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x44, 0x0a, 0x11, + 0x41, 0x64, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x32, 0x89, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x14, 0x2e, 0x61, + 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x50, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, + 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x19, 0x2e, 0x61, + 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x36, + 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x7a, 0x75, + 0x72, 0x65, 0x2f, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x6c, 0x69, + 0x2f, 0x61, 0x7a, 0x64, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x3b, + 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -102,21 +155,27 @@ func file_project_proto_rawDescGZIP() []byte { return file_project_proto_rawDescData } -var file_project_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_project_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_project_proto_goTypes = []any{ (*GetProjectResponse)(nil), // 0: azdext.GetProjectResponse - (*ProjectConfig)(nil), // 1: azdext.ProjectConfig - (*EmptyRequest)(nil), // 2: azdext.EmptyRequest + (*AddServiceRequest)(nil), // 1: azdext.AddServiceRequest + (*ProjectConfig)(nil), // 2: azdext.ProjectConfig + (*ServiceConfig)(nil), // 3: azdext.ServiceConfig + (*EmptyRequest)(nil), // 4: azdext.EmptyRequest + (*EmptyResponse)(nil), // 5: azdext.EmptyResponse } var file_project_proto_depIdxs = []int32{ - 1, // 0: azdext.GetProjectResponse.project:type_name -> azdext.ProjectConfig - 2, // 1: azdext.ProjectService.Get:input_type -> azdext.EmptyRequest - 0, // 2: azdext.ProjectService.Get:output_type -> azdext.GetProjectResponse - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 2, // 0: azdext.GetProjectResponse.project:type_name -> azdext.ProjectConfig + 3, // 1: azdext.AddServiceRequest.service:type_name -> azdext.ServiceConfig + 4, // 2: azdext.ProjectService.Get:input_type -> azdext.EmptyRequest + 1, // 3: azdext.ProjectService.AddService:input_type -> azdext.AddServiceRequest + 0, // 4: azdext.ProjectService.Get:output_type -> azdext.GetProjectResponse + 5, // 5: azdext.ProjectService.AddService:output_type -> azdext.EmptyResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_project_proto_init() } @@ -131,7 +190,7 @@ func file_project_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_project_proto_rawDesc, NumEnums: 0, - NumMessages: 1, + NumMessages: 2, NumExtensions: 0, NumServices: 1, }, diff --git a/cli/azd/pkg/azdext/project_grpc.pb.go b/cli/azd/pkg/azdext/project_grpc.pb.go index ccc547e9553..679bc367dc4 100644 --- a/cli/azd/pkg/azdext/project_grpc.pb.go +++ b/cli/azd/pkg/azdext/project_grpc.pb.go @@ -22,7 +22,8 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - ProjectService_Get_FullMethodName = "/azdext.ProjectService/Get" + ProjectService_Get_FullMethodName = "/azdext.ProjectService/Get" + ProjectService_AddService_FullMethodName = "/azdext.ProjectService/AddService" ) // ProjectServiceClient is the client API for ProjectService service. @@ -33,6 +34,7 @@ const ( type ProjectServiceClient interface { // Gets the current project. Get(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*GetProjectResponse, error) + AddService(ctx context.Context, in *AddServiceRequest, opts ...grpc.CallOption) (*EmptyResponse, error) } type projectServiceClient struct { @@ -53,6 +55,16 @@ func (c *projectServiceClient) Get(ctx context.Context, in *EmptyRequest, opts . return out, nil } +func (c *projectServiceClient) AddService(ctx context.Context, in *AddServiceRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(EmptyResponse) + err := c.cc.Invoke(ctx, ProjectService_AddService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // ProjectServiceServer is the server API for ProjectService service. // All implementations must embed UnimplementedProjectServiceServer // for forward compatibility. @@ -61,6 +73,7 @@ func (c *projectServiceClient) Get(ctx context.Context, in *EmptyRequest, opts . type ProjectServiceServer interface { // Gets the current project. Get(context.Context, *EmptyRequest) (*GetProjectResponse, error) + AddService(context.Context, *AddServiceRequest) (*EmptyResponse, error) mustEmbedUnimplementedProjectServiceServer() } @@ -74,6 +87,9 @@ type UnimplementedProjectServiceServer struct{} func (UnimplementedProjectServiceServer) Get(context.Context, *EmptyRequest) (*GetProjectResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") } +func (UnimplementedProjectServiceServer) AddService(context.Context, *AddServiceRequest) (*EmptyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddService not implemented") +} func (UnimplementedProjectServiceServer) mustEmbedUnimplementedProjectServiceServer() {} func (UnimplementedProjectServiceServer) testEmbeddedByValue() {} @@ -113,6 +129,24 @@ func _ProjectService_Get_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _ProjectService_AddService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProjectServiceServer).AddService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProjectService_AddService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProjectServiceServer).AddService(ctx, req.(*AddServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + // ProjectService_ServiceDesc is the grpc.ServiceDesc for ProjectService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -124,6 +158,10 @@ var ProjectService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Get", Handler: _ProjectService_Get_Handler, }, + { + MethodName: "AddService", + Handler: _ProjectService_AddService_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "project.proto", diff --git a/cli/azd/pkg/azdext/workflow.pb.go b/cli/azd/pkg/azdext/workflow.pb.go new file mode 100644 index 00000000000..d74880ec21f --- /dev/null +++ b/cli/azd/pkg/azdext/workflow.pb.go @@ -0,0 +1,307 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc v5.29.1 +// source: workflow.proto + +package azdext + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// RunWorkflowRequest is a request to run a workflow. +type RunWorkflowRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Workflow *Workflow `protobuf:"bytes,1,opt,name=workflow,proto3" json:"workflow,omitempty"` +} + +func (x *RunWorkflowRequest) Reset() { + *x = RunWorkflowRequest{} + mi := &file_workflow_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RunWorkflowRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunWorkflowRequest) ProtoMessage() {} + +func (x *RunWorkflowRequest) ProtoReflect() protoreflect.Message { + mi := &file_workflow_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunWorkflowRequest.ProtoReflect.Descriptor instead. +func (*RunWorkflowRequest) Descriptor() ([]byte, []int) { + return file_workflow_proto_rawDescGZIP(), []int{0} +} + +func (x *RunWorkflowRequest) GetWorkflow() *Workflow { + if x != nil { + return x.Workflow + } + return nil +} + +// Workflow is a collection of steps to be executed in order. +type Workflow struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Steps []*WorkflowStep `protobuf:"bytes,2,rep,name=steps,proto3" json:"steps,omitempty"` +} + +func (x *Workflow) Reset() { + *x = Workflow{} + mi := &file_workflow_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Workflow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Workflow) ProtoMessage() {} + +func (x *Workflow) ProtoReflect() protoreflect.Message { + mi := &file_workflow_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Workflow.ProtoReflect.Descriptor instead. +func (*Workflow) Descriptor() ([]byte, []int) { + return file_workflow_proto_rawDescGZIP(), []int{1} +} + +func (x *Workflow) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Workflow) GetSteps() []*WorkflowStep { + if x != nil { + return x.Steps + } + return nil +} + +// WorkflowStep is a single step in a workflow. +type WorkflowStep struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Command *WorkflowCommand `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` +} + +func (x *WorkflowStep) Reset() { + *x = WorkflowStep{} + mi := &file_workflow_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkflowStep) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkflowStep) ProtoMessage() {} + +func (x *WorkflowStep) ProtoReflect() protoreflect.Message { + mi := &file_workflow_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WorkflowStep.ProtoReflect.Descriptor instead. +func (*WorkflowStep) Descriptor() ([]byte, []int) { + return file_workflow_proto_rawDescGZIP(), []int{2} +} + +func (x *WorkflowStep) GetCommand() *WorkflowCommand { + if x != nil { + return x.Command + } + return nil +} + +// WorkflowCommand is a command to be executed in a workflow step. +type WorkflowCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Args []string `protobuf:"bytes,1,rep,name=args,proto3" json:"args,omitempty"` +} + +func (x *WorkflowCommand) Reset() { + *x = WorkflowCommand{} + mi := &file_workflow_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkflowCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkflowCommand) ProtoMessage() {} + +func (x *WorkflowCommand) ProtoReflect() protoreflect.Message { + mi := &file_workflow_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WorkflowCommand.ProtoReflect.Descriptor instead. +func (*WorkflowCommand) Descriptor() ([]byte, []int) { + return file_workflow_proto_rawDescGZIP(), []int{3} +} + +func (x *WorkflowCommand) GetArgs() []string { + if x != nil { + return x.Args + } + return nil +} + +var File_workflow_proto protoreflect.FileDescriptor + +var file_workflow_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x06, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x1a, 0x0c, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x42, 0x0a, 0x12, 0x52, 0x75, 0x6e, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x08, + 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x4a, 0x0a, 0x08, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x73, 0x74, + 0x65, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x7a, 0x64, 0x65, + 0x78, 0x74, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x65, 0x70, 0x52, + 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x22, 0x41, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x53, 0x74, 0x65, 0x70, 0x12, 0x31, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x25, 0x0a, 0x0f, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x61, 0x72, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, + 0x32, 0x4b, 0x0a, 0x0f, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x03, 0x52, 0x75, 0x6e, 0x12, 0x1a, 0x2e, 0x61, 0x7a, 0x64, + 0x65, 0x78, 0x74, 0x2e, 0x52, 0x75, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, + 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x7a, 0x75, 0x72, + 0x65, 0x2f, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x6c, 0x69, 0x2f, + 0x61, 0x7a, 0x64, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x7a, 0x64, 0x65, 0x78, 0x74, 0x3b, 0x61, + 0x7a, 0x64, 0x65, 0x78, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_workflow_proto_rawDescOnce sync.Once + file_workflow_proto_rawDescData = file_workflow_proto_rawDesc +) + +func file_workflow_proto_rawDescGZIP() []byte { + file_workflow_proto_rawDescOnce.Do(func() { + file_workflow_proto_rawDescData = protoimpl.X.CompressGZIP(file_workflow_proto_rawDescData) + }) + return file_workflow_proto_rawDescData +} + +var file_workflow_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_workflow_proto_goTypes = []any{ + (*RunWorkflowRequest)(nil), // 0: azdext.RunWorkflowRequest + (*Workflow)(nil), // 1: azdext.Workflow + (*WorkflowStep)(nil), // 2: azdext.WorkflowStep + (*WorkflowCommand)(nil), // 3: azdext.WorkflowCommand + (*EmptyResponse)(nil), // 4: azdext.EmptyResponse +} +var file_workflow_proto_depIdxs = []int32{ + 1, // 0: azdext.RunWorkflowRequest.workflow:type_name -> azdext.Workflow + 2, // 1: azdext.Workflow.steps:type_name -> azdext.WorkflowStep + 3, // 2: azdext.WorkflowStep.command:type_name -> azdext.WorkflowCommand + 0, // 3: azdext.WorkflowService.Run:input_type -> azdext.RunWorkflowRequest + 4, // 4: azdext.WorkflowService.Run:output_type -> azdext.EmptyResponse + 4, // [4:5] is the sub-list for method output_type + 3, // [3:4] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_workflow_proto_init() } +func file_workflow_proto_init() { + if File_workflow_proto != nil { + return + } + file_models_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_workflow_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_workflow_proto_goTypes, + DependencyIndexes: file_workflow_proto_depIdxs, + MessageInfos: file_workflow_proto_msgTypes, + }.Build() + File_workflow_proto = out.File + file_workflow_proto_rawDesc = nil + file_workflow_proto_goTypes = nil + file_workflow_proto_depIdxs = nil +} diff --git a/cli/azd/pkg/azdext/workflow_grpc.pb.go b/cli/azd/pkg/azdext/workflow_grpc.pb.go new file mode 100644 index 00000000000..4ea5c3f44f8 --- /dev/null +++ b/cli/azd/pkg/azdext/workflow_grpc.pb.go @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.1 +// source: workflow.proto + +package azdext + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + WorkflowService_Run_FullMethodName = "/azdext.WorkflowService/Run" +) + +// WorkflowServiceClient is the client API for WorkflowService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type WorkflowServiceClient interface { + // ListResources retrieves all configured composability resources in the current project. + Run(ctx context.Context, in *RunWorkflowRequest, opts ...grpc.CallOption) (*EmptyResponse, error) +} + +type workflowServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewWorkflowServiceClient(cc grpc.ClientConnInterface) WorkflowServiceClient { + return &workflowServiceClient{cc} +} + +func (c *workflowServiceClient) Run(ctx context.Context, in *RunWorkflowRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(EmptyResponse) + err := c.cc.Invoke(ctx, WorkflowService_Run_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// WorkflowServiceServer is the server API for WorkflowService service. +// All implementations must embed UnimplementedWorkflowServiceServer +// for forward compatibility. +type WorkflowServiceServer interface { + // ListResources retrieves all configured composability resources in the current project. + Run(context.Context, *RunWorkflowRequest) (*EmptyResponse, error) + mustEmbedUnimplementedWorkflowServiceServer() +} + +// UnimplementedWorkflowServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedWorkflowServiceServer struct{} + +func (UnimplementedWorkflowServiceServer) Run(context.Context, *RunWorkflowRequest) (*EmptyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Run not implemented") +} +func (UnimplementedWorkflowServiceServer) mustEmbedUnimplementedWorkflowServiceServer() {} +func (UnimplementedWorkflowServiceServer) testEmbeddedByValue() {} + +// UnsafeWorkflowServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to WorkflowServiceServer will +// result in compilation errors. +type UnsafeWorkflowServiceServer interface { + mustEmbedUnimplementedWorkflowServiceServer() +} + +func RegisterWorkflowServiceServer(s grpc.ServiceRegistrar, srv WorkflowServiceServer) { + // If the following call pancis, it indicates UnimplementedWorkflowServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&WorkflowService_ServiceDesc, srv) +} + +func _WorkflowService_Run_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RunWorkflowRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WorkflowServiceServer).Run(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WorkflowService_Run_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WorkflowServiceServer).Run(ctx, req.(*RunWorkflowRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// WorkflowService_ServiceDesc is the grpc.ServiceDesc for WorkflowService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var WorkflowService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "azdext.WorkflowService", + HandlerType: (*WorkflowServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Run", + Handler: _WorkflowService_Run_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "workflow.proto", +}