// 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, 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, 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, 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 } } } } }