This commit introduces a significant architectural refactoring to decouple the driven adapter from low-level infrastructure concerns, adhering more strictly to the principles of Hexagonal Architecture. Problem: The driven adapter in `internal/adapters/driven/edgeconnect` was responsible for both adapting data structures and handling direct HTTP communication, authentication, and request/response logic. This violated the separation of concerns, making the adapter difficult to test and maintain. Solution: A new infrastructure layer has been created at `internal/infrastructure`. This layer now contains all the low-level details of interacting with the EdgeConnect API. Key Changes: - **New Infrastructure Layer:** Created `internal/infrastructure` to house components that connect to external systems. - **Generic HTTP Client:** A new, generic `edgeconnect_client` was created in `internal/infrastructure/edgeconnect_client`. It is responsible for authentication, making HTTP requests, and handling raw responses. It has no knowledge of the application's domain models. - **Config & Transport Moved:** The `config` and `http` (now `transport`) packages were moved into the infrastructure layer, as they are details of how the application is configured and communicates. - **Consolidated Driven Adapter:** The logic from the numerous old adapter files (`apps.go`, `cloudlet.go`, etc.) has been consolidated into a single, true adapter at `internal/adapters/driven/edgeconnect/adapter.go`. - **Clear Responsibility:** The new `adapter.go` is now solely responsible for: 1. Implementing the driven port (repository) interfaces. 2. Translating domain models into the data structures required by the `edgeconnect_client`. 3. Calling the `edgeconnect_client` to perform the API operations. 4. Translating the results back into domain models. - **Updated Dependency Injection:** The application's entry point (`cmd/cli/main.go`) has been updated to construct and inject dependencies according to the new architecture: `infra_client` -> `adapter` -> `service` -> `cli_command`. - **SDK & Apply Command:** The SDK examples and the `apply` command have been updated to use the new adapter and its repository methods, removing all direct client instantiation.
176 lines
5.3 KiB
Go
176 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/driven/edgeconnect"
|
|
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/domain"
|
|
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/infrastructure/edgeconnect_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 client *edgeconnect_client.Client
|
|
|
|
if token != "" {
|
|
// Use static token authentication
|
|
fmt.Println("🔐 Using Bearer token authentication")
|
|
client = edgeconnect_client.NewClient(baseURL,
|
|
edgeconnect_client.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
|
edgeconnect_client.WithAuthProvider(edgeconnect_client.NewStaticTokenProvider(token)),
|
|
edgeconnect_client.WithLogger(log.Default()),
|
|
)
|
|
} else if username != "" && password != "" {
|
|
// Use username/password authentication (matches existing client pattern)
|
|
fmt.Println("🔐 Using username/password authentication")
|
|
client = edgeconnect_client.NewClientWithCredentials(baseURL, username, password,
|
|
edgeconnect_client.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
|
edgeconnect_client.WithLogger(log.Default()),
|
|
)
|
|
} else {
|
|
log.Fatal("Authentication required: Set either EDGEXR_TOKEN or both EDGEXR_USERNAME and EDGEXR_PASSWORD")
|
|
}
|
|
|
|
adapter := edgeconnect.NewAdapter(client)
|
|
|
|
ctx := context.Background()
|
|
|
|
// Example application to deploy
|
|
app := &NewAppInput{
|
|
Region: "EU",
|
|
App: App{
|
|
Key: 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: Flavor{Name: "EU.small"},
|
|
ServerlessConfig: struct{}{},
|
|
AllowServerless: false,
|
|
},
|
|
}
|
|
|
|
// Demonstrate app lifecycle
|
|
if err := demonstrateAppLifecycle(ctx, adapter, app); err != nil {
|
|
log.Fatalf("App lifecycle demonstration failed: %v", err)
|
|
}
|
|
|
|
fmt.Println("✅ SDK example completed successfully!")
|
|
}
|
|
|
|
func demonstrateAppLifecycle(ctx context.Context, adapter *edgeconnect.Adapter, input *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 := adapter.CreateApp(ctx, input.Region, domainApp); err != nil {
|
|
return fmt.Errorf("failed to create app: %+v", err)
|
|
}
|
|
fmt.Println(" ✅ App created successfully.")
|
|
|
|
// Defer cleanup to ensure the app is deleted even if subsequent steps fail
|
|
defer func() {
|
|
fmt.Println("\n4. Cleaning up: Deleting application...")
|
|
domainAppKey = domain.AppKey{
|
|
Organization: appKey.Organization,
|
|
Name: appKey.Name,
|
|
Version: appKey.Version,
|
|
}
|
|
if err := adapter.DeleteApp(ctx, region, domainAppKey); err != nil {
|
|
fmt.Printf(" ⚠️ Cleanup failed: %v\n", err)
|
|
} else {
|
|
fmt.Println(" ✅ App deleted successfully.")
|
|
}
|
|
}()
|
|
|
|
// Step 2: Verify app creation by fetching it
|
|
fmt.Println("\n2. Verifying app creation...")
|
|
domainAppKey = domain.AppKey{
|
|
Organization: appKey.Organization,
|
|
Name: appKey.Name,
|
|
Version: appKey.Version,
|
|
}
|
|
fetchedApp, err := adapter.ShowApp(ctx, region, domainAppKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get app after creation: %w", err)
|
|
}
|
|
fmt.Printf(" ✅ Fetched app: %s/%s v%s\n",
|
|
fetchedApp.Key.Organization, fetchedApp.Key.Name, fetchedApp.Key.Version)
|
|
|
|
// Step 3: (Placeholder for other operations like updating or deploying)
|
|
fmt.Println("\n3. Skipping further operations in this example.")
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper to get environment variables or return a default
|
|
func getEnvOrDefault(key, defaultValue string) string {
|
|
if value, exists := os.LookupEnv(key); exists {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// The structs below are included to make the example self-contained and runnable.
|
|
// In a real application, these would be defined in the `edgeconnect` package.
|
|
|
|
type NewAppInput struct {
|
|
Region string
|
|
App App
|
|
}
|
|
|
|
type App struct {
|
|
Key AppKey
|
|
Deployment string
|
|
ImageType string
|
|
ImagePath string
|
|
DefaultFlavor Flavor
|
|
ServerlessConfig interface{}
|
|
AllowServerless bool
|
|
}
|
|
|
|
type AppKey struct {
|
|
Organization string
|
|
Name string
|
|
Version string
|
|
}
|
|
|
|
type Flavor struct {
|
|
Name string
|
|
}
|