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>
This commit is contained in:
parent
9a06c608b2
commit
e6de69551e
4 changed files with 412 additions and 17 deletions
|
|
@ -16,20 +16,34 @@ import (
|
|||
|
||||
func main() {
|
||||
// Configure SDK client
|
||||
baseURL := getEnvOrDefault("EDGEXR_BASE_URL", "https://hub.apps.edge.platform.mg3.mdb.osc.live/api/v1")
|
||||
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", "")
|
||||
|
||||
if token == "" {
|
||||
log.Fatal("EDGEXR_TOKEN environment variable is required")
|
||||
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")
|
||||
}
|
||||
|
||||
// Create SDK client with authentication and logging
|
||||
client := client.NewClient(baseURL,
|
||||
client.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
||||
client.WithAuthProvider(client.NewStaticTokenProvider(token)),
|
||||
client.WithLogger(log.Default()),
|
||||
)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Example application to deploy
|
||||
|
|
@ -49,14 +63,14 @@ func main() {
|
|||
}
|
||||
|
||||
// Demonstrate app lifecycle
|
||||
if err := demonstrateAppLifecycle(ctx, client, app); err != nil {
|
||||
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, c *client.Client, input *client.NewAppInput) error {
|
||||
func demonstrateAppLifecycle(ctx context.Context, edgeClient *client.Client, input *client.NewAppInput) error {
|
||||
appKey := input.App.Key
|
||||
region := input.Region
|
||||
|
||||
|
|
@ -65,14 +79,14 @@ func demonstrateAppLifecycle(ctx context.Context, c *client.Client, input *clien
|
|||
|
||||
// Step 1: Create the application
|
||||
fmt.Println("\n1. Creating application...")
|
||||
if err := c.CreateApp(ctx, input); err != nil {
|
||||
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 := c.ShowApp(ctx, appKey, region)
|
||||
app, err := edgeClient.ShowApp(ctx, appKey, region)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to show app: %w", err)
|
||||
}
|
||||
|
|
@ -82,7 +96,7 @@ func demonstrateAppLifecycle(ctx context.Context, c *client.Client, input *clien
|
|||
// Step 3: List applications in the organization
|
||||
fmt.Println("\n3. Listing applications...")
|
||||
filter := client.AppKey{Organization: appKey.Organization}
|
||||
apps, err := c.ShowApps(ctx, filter, region)
|
||||
apps, err := edgeClient.ShowApps(ctx, filter, region)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list apps: %w", err)
|
||||
}
|
||||
|
|
@ -90,14 +104,14 @@ func demonstrateAppLifecycle(ctx context.Context, c *client.Client, input *clien
|
|||
|
||||
// Step 4: Clean up - delete the application
|
||||
fmt.Println("\n4. Cleaning up...")
|
||||
if err := c.DeleteApp(ctx, appKey, region); err != nil {
|
||||
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 = c.ShowApp(ctx, appKey, region)
|
||||
_, 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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue