Skip to content
Merged
17 changes: 16 additions & 1 deletion cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ words:
- jsonschema
- rustc
- figspec
- finetune
- bubbletea
- lipgloss
- gopxl
Expand Down Expand Up @@ -251,7 +252,21 @@ overrides:
- httptest
- Logf
- Getenv
- httptest
- httptest
- filename: pkg/extensions/metadata.go
words:
- invopop
- filename: pkg/extensions/schema_validator.go
words:
- invopop
- jsonschemav
- filename: pkg/project/dockerfile_builder.go
words:
- WORKDIR
- workdir
- filename: extensions/microsoft.azd.demo/internal/cmd/metadata.go
words:
- invopop
ignorePaths:
- "**/*_test.go"
- "**/mock*.go"
Expand Down
9 changes: 8 additions & 1 deletion cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -874,8 +874,15 @@ func registerCommonDependencies(container *ioc.NestedContainer) {

// Extensions
container.MustRegisterSingleton(extensions.NewManager)
container.MustRegisterSingleton(extensions.NewRunner)
container.MustRegisterSingleton(extensions.NewSourceManager)
container.MustRegisterSingleton(extensions.NewRunner)
container.MustRegisterSingleton(func(serviceLocator ioc.ServiceLocator) *lazy.Lazy[*extensions.Runner] {
return lazy.NewLazy(func() (*extensions.Runner, error) {
var runner *extensions.Runner
err := serviceLocator.Resolve(&runner)
return runner, err
})
})

// gRPC Server
container.MustRegisterScoped(grpcserver.NewServer)
Expand Down
8 changes: 7 additions & 1 deletion cli/azd/extensions/extension.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"capabilities": {
"type": "array",
"title": "Capabilities",
"description": "List of capabilities provided by the extension. Supported values: custom-commands, lifecycle-events, mcp-server, service-target-provider. Select one or more from the allowed list. Each value must be unique.",
"description": "List of capabilities provided by the extension. Supported values: custom-commands, lifecycle-events, mcp-server, service-target-provider, metadata. Select one or more from the allowed list. Each value must be unique.",
"minItems": 1,
"uniqueItems": true,
"items": {
Expand Down Expand Up @@ -142,6 +142,12 @@
"const": "framework-service-provider",
"title": "Framework Service Provider",
"description": "Framework service provider enables extensions to provide custom language frameworks and build systems."
},
{
"type": "string",
"const": "metadata",
"title": "Metadata",
"description": "Metadata capability enables extensions to provide comprehensive metadata about their commands and capabilities via a metadata command."
}
]
}
Expand Down
42 changes: 42 additions & 0 deletions cli/azd/extensions/microsoft.azd.demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,48 @@ Displays a list of Azure resources based on the current logged in user, and subs

Displays a list of Azure resources based on the current logged in user, subscription and resource group filtered by resource type and kind.

### `metadata`

The `metadata` command demonstrates the metadata capability, which provides command structure and configuration schemas.

#### Usage: `azd demo metadata`

This command generates JSON metadata including:
- **Command Tree**: All available commands, subcommands, flags, and arguments with descriptions
- **Configuration Schemas**: Type-safe JSON schemas for project and service-level configuration

Example configuration in `azure.yaml`:

```yaml
extensions:
demo:
project:
enableColors: true
maxItems: 20
labels:
team: "platform"
env: "dev"

services:
web:
extensions:
demo:
service:
endpoint: "https://api.example.com"
port: 8080
environment: "staging"
healthCheck:
enabled: true
path: "/health"
interval: 30
```

The metadata capability enables:
- IDE autocomplete and validation for extension configuration
- Automatic help generation
- Configuration validation before deployment
- Better discoverability of extension features

### `listen`

This `listen` command is required when your extension leverages `LifecycleEvents` capability.
Expand Down
1 change: 1 addition & 0 deletions cli/azd/extensions/microsoft.azd.demo/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ capabilities:
- mcp-server
- service-target-provider
- framework-service-provider
- metadata
providers:
- name: demo
type: service-target
Expand Down
88 changes: 88 additions & 0 deletions cli/azd/extensions/microsoft.azd.demo/internal/cmd/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package cmd

import (
"encoding/json"
"fmt"

"github.com/azure/azure-dev/cli/azd/extensions/microsoft.azd.demo/internal/config"
"github.com/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/azure/azure-dev/cli/azd/pkg/extensions"
"github.com/invopop/jsonschema"
"github.com/spf13/cobra"
)

func newMetadataCommand() *cobra.Command {
return &cobra.Command{
Use: "metadata",
Short: "Generate extension metadata including command structure and configuration schemas",
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
// Get root command for metadata generation
rootCmd := cmd.Root()

// Generate extension metadata with commands and configuration
metadata := azdext.GenerateExtensionMetadata(
"1.0", // schema version
"microsoft.azd.demo", // extension id
rootCmd,
)

// Add custom configuration schemas and environment variables
metadata.Configuration = generateConfigurationMetadata()

// Output as JSON
jsonBytes, err := json.MarshalIndent(metadata, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal metadata: %w", err)
}

fmt.Println(string(jsonBytes))
return nil
},
}
}

// generateConfigurationMetadata creates configuration schemas for the demo extension.
// This demonstrates how extension developers can define type-safe configuration
// requirements using Go structs and automatic schema generation.
func generateConfigurationMetadata() *extensions.ConfigurationMetadata {
// Generate schemas from Go types automatically
return &extensions.ConfigurationMetadata{
Global: jsonschema.Reflect(&config.CustomGlobalConfig{}),
Project: jsonschema.Reflect(&config.CustomProjectConfig{}),
Service: jsonschema.Reflect(&config.CustomServiceConfig{}),
EnvironmentVariables: generateEnvironmentVariables(),
}
}

// generateEnvironmentVariables documents environment variables used by the demo extension.
// This helps users understand what environment variables are available and how to use them.
func generateEnvironmentVariables() []extensions.EnvironmentVariable {
return []extensions.EnvironmentVariable{
{
Name: "DEMO_API_KEY",
Description: "API key for external service integration. Used for authentication with external services.",
Example: "sk-abc123xyz456",
},
{
Name: "DEMO_LOG_LEVEL",
Description: "Set logging verbosity level. Controls the amount of detail logged during operations.",
Default: "info",
Example: "debug",
},
{
Name: "DEMO_TIMEOUT",
Description: "Default timeout in seconds. Use for longer timeouts on slow networks or large transfers.",
Default: "30",
Example: "60",
},
{
Name: "DEMO_CACHE_DIR",
Description: "Custom directory path for extension cache files. Controls where temporary files are stored.",
Example: "/tmp/demo-cache",
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func NewRootCommand() *cobra.Command {
rootCmd.AddCommand(newVersionCommand())
rootCmd.AddCommand(newMcpCommand())
rootCmd.AddCommand(newGhUrlParseCommand())
rootCmd.AddCommand(newMetadataCommand())

return rootCmd
}
52 changes: 52 additions & 0 deletions cli/azd/extensions/microsoft.azd.demo/internal/config/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package config

// CustomGlobalConfig defines global-level configuration for the demo extension
// Uses namespacing to avoid conflicts with other extensions or azd configuration
type CustomGlobalConfig struct {
// Demo extension settings namespace
Demo *DemoGlobalSettings `json:"demo,omitempty" jsonschema:"description=Demo extension global settings"`
}

// DemoGlobalSettings contains the actual global configuration for the demo extension
type DemoGlobalSettings struct {
// API key for external services
APIKey string `json:"apiKey,omitempty" jsonschema:"description=API key for service integration"`
// Log level: debug, info, warn, error
LogLevel string `json:"logLevel,omitempty" jsonschema:"enum=debug,enum=info,enum=warn,enum=error"`
// Enable telemetry collection
EnableTelemetry bool `json:"enableTelemetry,omitempty" jsonschema:"description=Enable telemetry,default=false"`
// Timeout for operations in seconds (1-300)
Timeout int `json:"timeout,omitempty" jsonschema:"minimum=1,maximum=300,default=30"`
}

// CustomProjectConfig defines project-level configuration for the demo extension
type CustomProjectConfig struct {
// Demo feature flags for project-level configuration
EnableColors bool `json:"enableColors,omitempty" jsonschema:"description=Enable color output,default=true"`
// Maximum number of items to display
MaxItems int `json:"maxItems,omitempty" jsonschema:"description=Max items,minimum=1,maximum=100,default=10"`
// Project labels for demo purposes
Labels map[string]string `json:"labels,omitempty" jsonschema:"description=Custom project labels"`
}

// CustomServiceConfig defines service-level configuration for the demo extension
type CustomServiceConfig struct {
// Demo service endpoint configuration
Endpoint string `json:"endpoint" jsonschema:"required,description=Service endpoint URL,format=uri"`
// Port for the demo service
Port int `json:"port,omitempty" jsonschema:"description=Service port,minimum=1,maximum=65535,default=8080"`
// Environment for demo deployment
Environment string `json:"environment,omitempty" jsonschema:"enum=development,enum=staging,enum=production"`
// Health check configuration
HealthCheck *HealthCheckConfig `json:"healthCheck,omitempty" jsonschema:"description=Health check config"`
}

// HealthCheckConfig defines health check configuration
type HealthCheckConfig struct {
Enabled bool `json:"enabled,omitempty" jsonschema:"description=Enable health checks,default=true"`
Path string `json:"path,omitempty" jsonschema:"description=Health check path,default=/health"`
Interval int `json:"interval,omitempty" jsonschema:"description=Check interval (sec),minimum=5,maximum=300"`
}
Loading
Loading