edge-connect-client/sdk/examples/comprehensive/main.go
Patrick Sy da3c4a3e4c
All checks were successful
test / test (push) Successful in 54s
feat(api): Added AppKey property to ShowAppInstances
2025-11-13 15:40:36 +01:00

353 lines
13 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ABOUTME: Comprehensive EdgeXR SDK example demonstrating complete app deployment workflow
// ABOUTME: Shows app creation, instance deployment, cloudlet management, and cleanup
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
"edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect"
)
func main() {
// Configure SDK client
baseURL := getEnvOrDefault("EDGEXR_BASE_URL", "https://hub.apps.edge.platform.mg3.mdb.osc.live")
// Support both authentication methods
token := getEnvOrDefault("EDGEXR_TOKEN", "")
username := getEnvOrDefault("EDGEXR_USERNAME", "")
password := getEnvOrDefault("EDGEXR_PASSWORD", "")
var client *edgeconnect.Client
if token != "" {
fmt.Println("🔐 Using Bearer token authentication")
client = edgeconnect.NewClient(baseURL,
edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
edgeconnect.WithAuthProvider(edgeconnect.NewStaticTokenProvider(token)),
edgeconnect.WithLogger(log.Default()),
)
} else if username != "" && password != "" {
fmt.Println("🔐 Using username/password authentication")
client = 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()
// Configuration for the workflow
config := WorkflowConfig{
Organization: "edp2",
Region: "EU",
AppName: "edge-app-demo",
AppVersion: "1.0.0",
CloudletOrg: "TelekomOP",
CloudletName: "Munich",
InstanceName: "app-instance-1",
FlavorName: "EU.small",
}
fmt.Printf("🚀 Starting comprehensive EdgeXR workflow demonstration\n")
fmt.Printf("Organization: %s, Region: %s\n\n", config.Organization, config.Region)
// Run the complete workflow
if err := runComprehensiveWorkflow(ctx, client, config); err != nil {
log.Fatalf("Workflow failed: %v", err)
}
fmt.Println("\n✅ Comprehensive EdgeXR SDK workflow completed successfully!")
fmt.Println("\n📊 Summary:")
fmt.Println(" • Created and managed applications")
fmt.Println(" • Deployed and managed application instances")
fmt.Println(" • Queried cloudlet information")
fmt.Println(" • Demonstrated complete lifecycle management")
}
// WorkflowConfig holds configuration for the demonstration workflow
type WorkflowConfig struct {
Organization string
Region string
AppName string
AppVersion string
CloudletOrg string
CloudletName string
InstanceName string
FlavorName string
}
func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config WorkflowConfig) error {
fmt.Println("═══ Phase 1: Application Management ═══")
// 1. Create Application
fmt.Println("\n1⃣ Creating application...")
app := &edgeconnect.NewAppInput{
Region: config.Region,
App: edgeconnect.App{
Key: edgeconnect.AppKey{
Organization: config.Organization,
Name: config.AppName,
Version: config.AppVersion,
},
Deployment: "kubernetes",
ImageType: "ImageTypeDocker", // field is ignored
ImagePath: "https://registry-1.docker.io/library/nginx:latest", // must be set. Even for kubernetes
DefaultFlavor: edgeconnect.Flavor{Name: config.FlavorName},
ServerlessConfig: struct{}{}, // must be set
AllowServerless: true, // must be set to true for kubernetes
RequiredOutboundConnections: []edgeconnect.SecurityRule{
{
Protocol: "tcp",
PortRangeMin: 80,
PortRangeMax: 80,
RemoteCIDR: "0.0.0.0/0",
},
{
Protocol: "tcp",
PortRangeMin: 443,
PortRangeMax: 443,
RemoteCIDR: "0.0.0.0/0",
},
},
},
}
if err := c.CreateApp(ctx, app); err != nil {
return fmt.Errorf("failed to create app: %w", err)
}
fmt.Printf("✅ App created: %s/%s v%s\n", config.Organization, config.AppName, config.AppVersion)
// 2. Show Application Details
fmt.Println("\n2⃣ Querying application details...")
appKey := edgeconnect.AppKey{
Organization: config.Organization,
Name: config.AppName,
Version: config.AppVersion,
}
appDetails, err := c.ShowApp(ctx, appKey, config.Region)
if err != nil {
return fmt.Errorf("failed to show app: %w", err)
}
fmt.Printf("✅ App details retrieved:\n")
fmt.Printf(" • Name: %s/%s v%s\n", appDetails.Key.Organization, appDetails.Key.Name, appDetails.Key.Version)
fmt.Printf(" • Deployment: %s\n", appDetails.Deployment)
fmt.Printf(" • Image: %s\n", appDetails.ImagePath)
fmt.Printf(" • Security Rules: %d configured\n", len(appDetails.RequiredOutboundConnections))
// 3. List Applications in Organization
fmt.Println("\n3⃣ Listing applications in organization...")
filter := edgeconnect.AppKey{Organization: config.Organization}
apps, err := c.ShowApps(ctx, filter, config.Region)
if err != nil {
return fmt.Errorf("failed to list apps: %w", err)
}
fmt.Printf("✅ Found %d applications in organization '%s'\n", len(apps), config.Organization)
for i, app := range apps {
fmt.Printf(" %d. %s v%s (%s)\n", i+1, app.Key.Name, app.Key.Version, app.Deployment)
}
fmt.Println("\n═══ Phase 2: Application Instance Management ═══")
// 4. Create Application Instance
fmt.Println("\n4⃣ Creating application instance...")
instance := &edgeconnect.NewAppInstanceInput{
Region: config.Region,
AppInst: edgeconnect.AppInstance{
Key: edgeconnect.AppInstanceKey{
Organization: config.Organization,
Name: config.InstanceName,
CloudletKey: edgeconnect.CloudletKey{
Organization: config.CloudletOrg,
Name: config.CloudletName,
},
},
AppKey: appKey,
Flavor: edgeconnect.Flavor{Name: config.FlavorName},
},
}
if err := c.CreateAppInstance(ctx, instance); err != nil {
return fmt.Errorf("failed to create app instance: %w", err)
}
fmt.Printf("✅ App instance created: %s on cloudlet %s/%s\n",
config.InstanceName, config.CloudletOrg, config.CloudletName)
// 5. Wait for Application Instance to be Ready
fmt.Println("\n5⃣ Waiting for application instance to be ready...")
instanceKey := edgeconnect.AppInstanceKey{
Organization: config.Organization,
Name: config.InstanceName,
CloudletKey: edgeconnect.CloudletKey{
Organization: config.CloudletOrg,
Name: config.CloudletName,
},
}
instanceDetails, err := waitForInstanceReady(ctx, c, instanceKey, edgeconnect.AppKey{}, config.Region, 5*time.Minute)
if err != nil {
return fmt.Errorf("failed to wait for instance ready: %w", err)
}
fmt.Printf("✅ Instance is ready:\n")
fmt.Printf(" • Name: %s\n", instanceDetails.Key.Name)
fmt.Printf(" • App: %s/%s v%s\n", instanceDetails.AppKey.Organization, instanceDetails.AppKey.Name, instanceDetails.AppKey.Version)
fmt.Printf(" • Cloudlet: %s/%s\n", instanceDetails.Key.CloudletKey.Organization, instanceDetails.Key.CloudletKey.Name)
fmt.Printf(" • Flavor: %s\n", instanceDetails.Flavor.Name)
fmt.Printf(" • State: %s\n", instanceDetails.State)
fmt.Printf(" • Power State: %s\n", instanceDetails.PowerState)
// 6. List Application Instances
fmt.Println("\n6⃣ Listing application instances...")
instances, err := c.ShowAppInstances(ctx, edgeconnect.AppInstanceKey{Organization: config.Organization}, config.Region)
if err != nil {
return fmt.Errorf("failed to list app instances: %w", err)
}
fmt.Printf("✅ Found %d application instances in organization '%s'\n", len(instances), config.Organization)
for i, inst := range instances {
fmt.Printf(" %d. %s (state: %s, cloudlet: %s)\n",
i+1, inst.Key.Name, inst.State, inst.Key.CloudletKey.Name)
}
// 7. Refresh Application Instance
fmt.Println("\n7⃣ Refreshing application instance...")
if err := c.RefreshAppInstance(ctx, instanceKey, config.Region); err != nil {
return fmt.Errorf("failed to refresh app instance: %w", err)
}
fmt.Printf("✅ Instance refreshed: %s\n", config.InstanceName)
fmt.Println("\n═══ Phase 3: Cloudlet Information ═══")
// 8. Show Cloudlet Details
fmt.Println("\n8⃣ Querying cloudlet information...")
cloudletKey := edgeconnect.CloudletKey{
Organization: config.CloudletOrg,
Name: config.CloudletName,
}
cloudlets, err := c.ShowCloudlets(ctx, cloudletKey, config.Region)
if err != nil {
// This might fail in demo environment, so we'll continue
fmt.Printf("⚠️ Could not retrieve cloudlet details: %v\n", err)
} else {
fmt.Printf("✅ Found %d cloudlets matching criteria\n", len(cloudlets))
for i, cloudlet := range cloudlets {
fmt.Printf(" %d. %s/%s (state: %s)\n",
i+1, cloudlet.Key.Organization, cloudlet.Key.Name, cloudlet.State)
fmt.Printf(" Location: lat=%.4f, lng=%.4f\n",
cloudlet.Location.Latitude, cloudlet.Location.Longitude)
}
}
// 9. Try to Get Cloudlet Manifest (may not be available in demo)
fmt.Println("\n9⃣ Attempting to retrieve cloudlet manifest...")
manifest, err := c.GetCloudletManifest(ctx, cloudletKey, config.Region)
if err != nil {
fmt.Printf("⚠️ Could not retrieve cloudlet manifest: %v\n", err)
} else {
fmt.Printf("✅ Cloudlet manifest retrieved (%d bytes)\n", len(manifest.Manifest))
}
// 10. Try to Get Cloudlet Resource Usage (may not be available in demo)
fmt.Println("\n🔟 Attempting to retrieve cloudlet resource usage...")
usage, err := c.GetCloudletResourceUsage(ctx, cloudletKey, config.Region)
if err != nil {
fmt.Printf("⚠️ Could not retrieve cloudlet usage: %v\n", err)
} else {
fmt.Printf("✅ Cloudlet resource usage retrieved\n")
for resource, value := range usage.Usage {
fmt.Printf(" • %s: %v\n", resource, value)
}
}
fmt.Println("\n═══ Phase 4: Cleanup ═══")
// 11. Delete Application Instance
fmt.Println("\n1⃣1⃣ Cleaning up application instance...")
if err := c.DeleteAppInstance(ctx, instanceKey, config.Region); err != nil {
return fmt.Errorf("failed to delete app instance: %w", err)
}
fmt.Printf("✅ App instance deleted: %s\n", config.InstanceName)
// 12. Delete Application
fmt.Println("\n1⃣2⃣ Cleaning up application...")
if err := c.DeleteApp(ctx, appKey, config.Region); err != nil {
return fmt.Errorf("failed to delete app: %w", err)
}
fmt.Printf("✅ App deleted: %s/%s v%s\n", config.Organization, config.AppName, config.AppVersion)
// 13. Verify Cleanup
fmt.Println("\n1⃣3⃣ Verifying cleanup...")
_, err = c.ShowApp(ctx, appKey, config.Region)
if err != nil && fmt.Sprintf("%v", err) == edgeconnect.ErrResourceNotFound.Error() {
fmt.Printf("✅ Cleanup verified - app no longer exists\n")
} else if err != nil {
fmt.Printf("✅ Cleanup appears successful (verification returned: %v)\n", err)
} else {
fmt.Printf("⚠️ App may still exist after deletion\n")
}
return nil
}
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// waitForInstanceReady polls the instance status until it's no longer "Creating" or timeout
func waitForInstanceReady(ctx context.Context, c *edgeconnect.Client, instanceKey edgeconnect.AppInstanceKey, appKey edgeconnect.AppKey, region string, timeout time.Duration) (edgeconnect.AppInstance, error) {
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
ticker := time.NewTicker(10 * time.Second) // Poll every 10 seconds
defer ticker.Stop()
fmt.Printf(" Polling instance state (timeout: %.0f minutes)...\n", timeout.Minutes())
for {
select {
case <-timeoutCtx.Done():
return edgeconnect.AppInstance{}, fmt.Errorf("timeout waiting for instance to be ready after %v", timeout)
case <-ticker.C:
instance, err := c.ShowAppInstance(timeoutCtx, instanceKey, appKey, region)
if err != nil {
// Log error but continue polling
fmt.Printf(" ⚠️ Error checking instance state: %v\n", err)
continue
}
fmt.Printf(" 📊 Instance state: %s", instance.State)
if instance.PowerState != "" {
fmt.Printf(" (power: %s)", instance.PowerState)
}
fmt.Printf("\n")
// Check if instance is ready (not in creating state)
state := strings.ToLower(instance.State)
if state != "" && state != "creating" && state != "create requested" {
if state == "ready" || state == "running" {
fmt.Printf(" ✅ Instance reached ready state: %s\n", instance.State)
return instance, nil
} else if state == "error" || state == "failed" || strings.Contains(state, "error") {
return instance, fmt.Errorf("instance entered error state: %s", instance.State)
} else {
// Instance is in some other stable state (not creating)
fmt.Printf(" ✅ Instance reached stable state: %s\n", instance.State)
return instance, nil
}
}
}
}
}