edge-connect-client/sdk/examples/deploy_app.go
Waldemar e6de69551e feat(sdk): Add username/password authentication matching existing client
Implemented dynamic token authentication using existing RetrieveToken pattern:

## Authentication Enhancements:
- **UsernamePasswordProvider**: Implements existing `POST /api/v1/login` flow
- **Token Caching**: 1-hour cache with thread-safe refresh logic
- **NewClientWithCredentials()**: Convenience constructor for username/password auth
- **Dual Auth Support**: Both static token and dynamic username/password flows

## Key Features:
- **Exact API Match**: Mirrors existing `client/client.go RetrieveToken()` implementation
- **Thread Safety**: Concurrent token refresh with mutex protection
- **Caching Strategy**: Reduces login calls, configurable expiry
- **Error Handling**: Structured login failures with context
- **Token Invalidation**: Manual cache clearing for token refresh

## Implementation Details:
```go
// Static token (existing)
client := client.NewClient(baseURL,
  client.WithAuthProvider(client.NewStaticTokenProvider(token)))

// Username/password (new - matches existing pattern)
client := client.NewClientWithCredentials(baseURL, username, password)
```

## Testing:
- **Comprehensive Auth Tests**: Login success/failure, caching, expiry
- **Mock Server Tests**: httptest-based token flow validation
- **Concurrent Safety**: Token refresh under concurrent access
- **Updated Examples**: Support both auth methods

## Backward Compatibility:
- Existing StaticTokenProvider unchanged
- All existing APIs maintain same signatures
- Example updated to support both auth methods via environment variables

This matches the existing prototype's authentication exactly while adding
production features like caching and thread safety.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 14:21:31 +02:00

133 lines
No EOL
4.2 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/sdk/client"
)
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 *client.Client
if token != "" {
// Use static token authentication
fmt.Println("🔐 Using Bearer token authentication")
edgeClient = client.NewClient(baseURL,
client.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
client.WithAuthProvider(client.NewStaticTokenProvider(token)),
client.WithLogger(log.Default()),
)
} else if username != "" && password != "" {
// Use username/password authentication (matches existing client pattern)
fmt.Println("🔐 Using username/password authentication")
edgeClient = client.NewClientWithCredentials(baseURL, username, password,
client.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
client.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 := &client.NewAppInput{
Region: "us-west",
App: client.App{
Key: client.AppKey{
Organization: "myorg",
Name: "my-edge-app",
Version: "1.0.0",
},
Deployment: "kubernetes",
ImageType: "ImageTypeDocker",
ImagePath: "nginx:latest",
DefaultFlavor: client.Flavor{Name: "m4.small"},
},
}
// 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 *client.Client, input *client.NewAppInput) error {
appKey := input.App.Key
region := input.Region
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...")
if err := edgeClient.CreateApp(ctx, input); err != nil {
return fmt.Errorf("failed to create app: %w", 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...")
app, err := edgeClient.ShowApp(ctx, appKey, region)
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 := client.AppKey{Organization: appKey.Organization}
apps, err := edgeClient.ShowApps(ctx, filter, region)
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...")
if err := edgeClient.DeleteApp(ctx, appKey, region); 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...")
_, err = edgeClient.ShowApp(ctx, appKey, region)
if err != nil {
if fmt.Sprintf("%v", err) == client.ErrResourceNotFound.Error() {
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
}