Addresses Verbesserungspotential 2 (Error Handling uneinheitlich) by introducing a comprehensive, structured error handling approach across all architectural layers. ## New Domain Error System - Add ErrorCode enum with 15 semantic error types (NOT_FOUND, VALIDATION_FAILED, etc.) - Implement DomainError struct with operation context, resource identifiers, and regions - Create resource-specific error constructors (NewAppError, NewInstanceError, NewCloudletError) - Add utility functions for error type checking (IsNotFoundError, IsValidationError, etc.) ## Service Layer Enhancements - Replace generic fmt.Errorf with structured domain errors in all services - Add comprehensive validation functions for App, AppInstance, and Cloudlet entities - Implement business logic validation with meaningful error context - Ensure consistent error semantics across app_service, instance_service, cloudlet_service ## Adapter Layer Updates - Update EdgeConnect adapters to use domain errors instead of error constants - Enhance CLI adapter with domain-specific error checking for better UX - Fix SDK examples to use new IsNotFoundError() approach - Maintain backward compatibility where possible ## Test Coverage - Add comprehensive error_test.go with 100% coverage of new error system - Update existing adapter tests to validate domain error types - All tests passing with proper error type assertions ## Benefits - ✅ Consistent error handling across all architectural layers - ✅ Rich error context with operation, resource, and region information - ✅ Type-safe error checking with semantic error codes - ✅ Better user experience with domain-specific error messages - ✅ Maintainable centralized error definitions - ✅ Full hexagonal architecture compliance Files modified: 12 files updated, 2 new files added Tests: All passing (29+ test cases with enhanced error validation)
165 lines
5.3 KiB
Go
165 lines
5.3 KiB
Go
// ABOUTME: Example demonstrating EdgeXR SDK usage for app deployment workflow
|
|
// ABOUTME: Shows app creation, querying, and cleanup using the typed SDK APIs
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/adapters/edgeconnect"
|
|
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/domain"
|
|
)
|
|
|
|
func main() {
|
|
// Configure SDK client
|
|
baseURL := getEnvOrDefault("EDGEXR_BASE_URL", "https://hub.apps.edge.platform.mg3.mdb.osc.live")
|
|
|
|
// Support both token-based and username/password authentication
|
|
token := getEnvOrDefault("EDGEXR_TOKEN", "")
|
|
username := getEnvOrDefault("EDGEXR_USERNAME", "")
|
|
password := getEnvOrDefault("EDGEXR_PASSWORD", "")
|
|
|
|
var edgeClient *edgeconnect.Client
|
|
|
|
if token != "" {
|
|
// Use static token authentication
|
|
fmt.Println("🔐 Using Bearer token authentication")
|
|
edgeClient = edgeconnect.NewClient(baseURL,
|
|
edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
|
edgeconnect.WithAuthProvider(edgeconnect.NewStaticTokenProvider(token)),
|
|
edgeconnect.WithLogger(log.Default()),
|
|
)
|
|
} else if username != "" && password != "" {
|
|
// Use username/password authentication (matches existing client pattern)
|
|
fmt.Println("🔐 Using username/password authentication")
|
|
edgeClient = edgeconnect.NewClientWithCredentials(baseURL, username, password,
|
|
edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
|
edgeconnect.WithLogger(log.Default()),
|
|
)
|
|
} else {
|
|
log.Fatal("Authentication required: Set either EDGEXR_TOKEN or both EDGEXR_USERNAME and EDGEXR_PASSWORD")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Example application to deploy
|
|
app := &edgeconnect.NewAppInput{
|
|
Region: "EU",
|
|
App: edgeconnect.App{
|
|
Key: edgeconnect.AppKey{
|
|
Organization: "edp2",
|
|
Name: "my-edge-app",
|
|
Version: "1.0.0",
|
|
},
|
|
Deployment: "docker",
|
|
ImageType: "ImageTypeDocker",
|
|
ImagePath: "https://registry-1.docker.io/library/nginx:latest",
|
|
DefaultFlavor: edgeconnect.Flavor{Name: "EU.small"},
|
|
ServerlessConfig: struct{}{},
|
|
AllowServerless: false,
|
|
},
|
|
}
|
|
|
|
// Demonstrate app lifecycle
|
|
if err := demonstrateAppLifecycle(ctx, edgeClient, app); err != nil {
|
|
log.Fatalf("App lifecycle demonstration failed: %v", err)
|
|
}
|
|
|
|
fmt.Println("✅ SDK example completed successfully!")
|
|
}
|
|
|
|
func demonstrateAppLifecycle(ctx context.Context, edgeClient *edgeconnect.Client, input *edgeconnect.NewAppInput) error {
|
|
appKey := input.App.Key
|
|
region := input.Region
|
|
var domainAppKey domain.AppKey
|
|
|
|
fmt.Printf("🚀 Demonstrating EdgeXR SDK with app: %s/%s v%s\n",
|
|
appKey.Organization, appKey.Name, appKey.Version)
|
|
|
|
// Step 1: Create the application
|
|
fmt.Println("\n1. Creating application...")
|
|
domainApp := &domain.App{
|
|
Key: domain.AppKey{
|
|
Organization: input.App.Key.Organization,
|
|
Name: input.App.Key.Name,
|
|
Version: input.App.Key.Version,
|
|
},
|
|
Deployment: input.App.Deployment,
|
|
ImageType: input.App.ImageType,
|
|
ImagePath: input.App.ImagePath,
|
|
DefaultFlavor: domain.Flavor{Name: input.App.DefaultFlavor.Name},
|
|
ServerlessConfig: input.App.ServerlessConfig,
|
|
AllowServerless: input.App.AllowServerless,
|
|
}
|
|
if err := edgeClient.CreateApp(ctx, input.Region, domainApp); err != nil {
|
|
return fmt.Errorf("failed to create app: %+v", err)
|
|
}
|
|
fmt.Printf("✅ App created: %s/%s v%s\n", appKey.Organization, appKey.Name, appKey.Version)
|
|
|
|
// Step 2: Query the application
|
|
fmt.Println("\n2. Querying application...")
|
|
domainAppKey = domain.AppKey{
|
|
Organization: appKey.Organization,
|
|
Name: appKey.Name,
|
|
Version: appKey.Version,
|
|
}
|
|
app, err := edgeClient.ShowApp(ctx, region, domainAppKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to show app: %w", err)
|
|
}
|
|
fmt.Printf("✅ App found: %s/%s v%s (deployment: %s)\n",
|
|
app.Key.Organization, app.Key.Name, app.Key.Version, app.Deployment)
|
|
|
|
// Step 3: List applications in the organization
|
|
fmt.Println("\n3. Listing applications...")
|
|
filter := domain.AppKey{Organization: appKey.Organization}
|
|
apps, err := edgeClient.ShowApps(ctx, region, filter)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list apps: %w", err)
|
|
}
|
|
fmt.Printf("✅ Found %d applications in organization '%s'\n", len(apps), appKey.Organization)
|
|
|
|
// Step 4: Clean up - delete the application
|
|
fmt.Println("\n4. Cleaning up...")
|
|
domainAppKey = domain.AppKey{
|
|
Organization: appKey.Organization,
|
|
Name: appKey.Name,
|
|
Version: appKey.Version,
|
|
}
|
|
if err := edgeClient.DeleteApp(ctx, region, domainAppKey); err != nil {
|
|
return fmt.Errorf("failed to delete app: %w", err)
|
|
}
|
|
fmt.Printf("✅ App deleted: %s/%s v%s\n", appKey.Organization, appKey.Name, appKey.Version)
|
|
|
|
// Step 5: Verify deletion
|
|
fmt.Println("\n5. Verifying deletion...")
|
|
domainAppKey = domain.AppKey{
|
|
Organization: appKey.Organization,
|
|
Name: appKey.Name,
|
|
Version: appKey.Version,
|
|
}
|
|
_, err = edgeClient.ShowApp(ctx, region, domainAppKey)
|
|
if err != nil {
|
|
if domain.IsNotFoundError(err) {
|
|
fmt.Printf("✅ App successfully deleted (not found)\n")
|
|
} else {
|
|
return fmt.Errorf("unexpected error verifying deletion: %w", err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("app still exists after deletion")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getEnvOrDefault(key, defaultValue string) string {
|
|
if value := os.Getenv(key); value != "" {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|