Skip to content

gRPC: Implement server-side ProvisioningService handler #7474

Description

@wbreza

Summary

Create the server-side ProvisioningService gRPC handler in internal/grpcserver/provisioning_service.go. This component runs in the azd core process and handles the bidirectional stream with extension processes. It extracts JWT claims, verifies capabilities, creates a MessageBroker, and registers ExternalProvisioningProvider instances in the IoC container when extensions register their providers.

Parent Epic

Part of #7465 — Provisioning Providers in the AZD Extension Framework

Context

Pattern to Follow

ServiceTargetService in internal/grpcserver/service_target_service.go is the exact model:

  1. Extract extension claims from JWT in gRPC context
  2. Verify extension has the required capability
  3. Create MessageBroker for the stream
  4. Register handler for the registration request type
  5. On registration: create adapter, register in IoC, send response
  6. broker.Run(ctx) — block until stream closes
  7. Cleanup on stream close

Detailed Requirements

Struct

type ProvisioningService struct {
    azdext.UnimplementedProvisioningServiceServer
    container        *ioc.NestedContainer
    extensionManager *extensions.Manager
    lazyEnv          *lazy.Lazy[*environment.Environment]
    providerMap      map[string]*grpcbroker.MessageBroker[azdext.ProvisioningMessage]
    providerMapMu    sync.Mutex
}

Constructor

func NewProvisioningService(
    container *ioc.NestedContainer,
    extensionManager *extensions.Manager,
    lazyEnv *lazy.Lazy[*environment.Environment],
) *ProvisioningService

Stream() Handler Flow

  1. Extract extension claims: extensions.ClaimsFromContext(stream.Context())
  2. Verify capability: check claims include ProvisioningProviderCapability
  3. Create MessageBroker[ProvisioningMessage] with ProvisioningEnvelope
  4. Register handler for RegisterProvisioningProviderRequest:
    • Extract name from request
    • Resolve extension from extensionManager
    • Create ExternalProvisioningProvider via factory
    • Register as named transient in IoC: container.RegisterNamedTransient(name, factory)
    • Store broker in providerMap[name]
    • Return RegisterProvisioningProviderResponse
  5. Call broker.Run(ctx) — blocks until stream closes
  6. Cleanup: remove from providerMap

IoC Registration Pattern

err := s.container.RegisterNamedTransient(providerName, func(
    console input.Console,
    prompter prompt.Prompter,
) provisioning.Provider {
    env, _ := s.lazyEnv.GetValue()
    return external.NewExternalProvisioningProvider(
        providerName,
        extension,
        broker,
        console,
        prompter,
        env,
    )
})

Note: Uses RegisterNamedTransient (not Singleton) because the provisioning provider may need fresh instances per resolution.

Acceptance Criteria

  • ProvisioningService struct embedding UnimplementedProvisioningServiceServer
  • NewProvisioningService() constructor with IoC-compatible signature
  • Stream() extracts claims via extensions.ClaimsFromContext()
  • Stream() verifies ProvisioningProviderCapability
  • Creates MessageBroker with ProvisioningEnvelope
  • Handles RegisterProvisioningProviderRequest
  • Registration creates ExternalProvisioningProvider and registers as named transient
  • Sends RegisterProvisioningProviderResponse
  • Calls broker.Run(ctx) (blocking)
  • Cleanup removes broker from providerMap on close
  • Provider name used as IoC key

Dependencies

Files

  • Create: internal/grpcserver/provisioning_service.go

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions