@@ -21,6 +21,9 @@ func e2e(
2121 ctx context.Context ,
2222 source * dagger.Directory ,
2323 opServiceAccount * dagger.Secret ,
24+ appID string ,
25+ customerID string ,
26+ sdkImage string ,
2427 licenseID string ,
2528 distribution string ,
2629 version string ,
@@ -416,22 +419,22 @@ spec:
416419 fmt .Println ("SDK logs after minimal RBAC test:" )
417420 fmt .Println (out )
418421
419- // Extract appID from the SDK logs
420- var appID string
422+ // Extract instanceAppID from the SDK logs
423+ var instanceAppID string
421424 lines := strings .Split (out , "\n " )
422- appIDRegex := regexp .MustCompile (`appID:\s+([a-f0-9-]+)` )
425+ instanceAppIDRegex := regexp .MustCompile (`appID:\s+([a-f0-9-]+)` )
423426 for _ , line := range lines {
424- if match := appIDRegex .FindStringSubmatch (line ); match != nil {
425- appID = match [1 ]
426- fmt .Printf ("Extracted appID : %s\n " , appID )
427+ if match := instanceAppIDRegex .FindStringSubmatch (line ); match != nil {
428+ instanceAppID = match [1 ]
429+ fmt .Printf ("Extracted instanceAppID : %s\n " , instanceAppID )
427430 break
428431 }
429432 }
430- if appID == "" {
431- return fmt .Errorf ("appID not found in SDK logs" )
433+ if instanceAppID == "" {
434+ return fmt .Errorf ("instanceAppID not found in SDK logs" )
432435 }
433436
434- // make a request to https://api.replicated.com/v1/instance/{appid }/events?pageSize=500
437+ // make a request to https://api.replicated.com/v1/instance/{instanceID }/events?pageSize=500
435438 tokenPlaintext , err := replicatedServiceAccount .Plaintext (ctx )
436439 if err != nil {
437440 return fmt .Errorf ("failed to get service account token: %w" , err )
@@ -446,7 +449,7 @@ spec:
446449 }
447450
448451 // Retry up to 5 times with 30 seconds between attempts
449- err = waitForResourcesReady (ctx , resourceNames , 30 , 5 * time .Second , tokenPlaintext , appID , distribution )
452+ err = waitForResourcesReady (ctx , resourceNames , 30 , 5 * time .Second , tokenPlaintext , instanceAppID , distribution )
450453 if err != nil {
451454 return fmt .Errorf ("failed to wait for resources to be ready: %w" , err )
452455 }
@@ -465,12 +468,46 @@ spec:
465468 {Kind : "deployment" , Name : "second-test-chart" },
466469 {Kind : "service" , Name : "replicated" },
467470 }
468- err = waitForResourcesReady (ctx , newResourceNames , 30 , 5 * time .Second , tokenPlaintext , appID , distribution )
471+ err = waitForResourcesReady (ctx , newResourceNames , 30 , 5 * time .Second , tokenPlaintext , instanceAppID , distribution )
469472 if err != nil {
470473 return fmt .Errorf ("failed to wait for resources to be ready: %w" , err )
471474 }
472475
473476 fmt .Printf ("E2E test for distribution %s and version %s passed\n " , distribution , version )
477+
478+ // Validate running images via vendor API
479+ // 1. Call vendor API to get running images for this instance
480+ imagesSet , err := getRunningImages (ctx , appID , customerID , instanceAppID , tokenPlaintext )
481+ if err != nil {
482+ return fmt .Errorf ("failed to get running images: %w" , err )
483+ }
484+
485+ // 2. Validate expected images
486+ required := []string {"docker.io/library/nginx:latest" , "docker.io/library/nginx:alpine" , strings .TrimSpace (sdkImage )}
487+ forbidden := []string {"docker.io/alpine/curl:latest" }
488+ missing := []string {}
489+ for _ , img := range required {
490+ if img == "" {
491+ continue
492+ }
493+ if _ , ok := imagesSet [img ]; ! ok {
494+ missing = append (missing , img )
495+ }
496+ }
497+ if len (missing ) > 0 {
498+ // Build a small preview of what we saw for debugging
499+ seen := make ([]string , 0 , len (imagesSet ))
500+ for k := range imagesSet {
501+ seen = append (seen , k )
502+ }
503+ return fmt .Errorf ("running images missing expected entries: %v. Seen: %v" , missing , seen )
504+ }
505+ for _ , img := range forbidden {
506+ if _ , ok := imagesSet [img ]; ok {
507+ return fmt .Errorf ("running images contains forbidden entry: %s" , img )
508+ }
509+ }
510+
474511 return nil
475512}
476513
@@ -490,8 +527,8 @@ type Event struct {
490527 } `json:"meta"`
491528}
492529
493- func getEvents (ctx context.Context , authToken string , appID string ) ([]Event , error ) {
494- url := fmt .Sprintf ("https://api.replicated.com/v1/instance/%s/events?pageSize=500" , appID )
530+ func getEvents (ctx context.Context , authToken string , instanceAppID string ) ([]Event , error ) {
531+ url := fmt .Sprintf ("https://api.replicated.com/v1/instance/%s/events?pageSize=500" , instanceAppID )
495532 req , err := http .NewRequestWithContext (ctx , "GET" , url , nil )
496533 if err != nil {
497534 return nil , fmt .Errorf ("failed to create request: %w" , err )
@@ -561,12 +598,12 @@ type Resource struct {
561598 Name string
562599}
563600
564- func waitForResourcesReady (ctx context.Context , resources []Resource , maxRetries int , retryInterval time.Duration , authToken string , appID string , distribution string ) error {
601+ func waitForResourcesReady (ctx context.Context , resources []Resource , maxRetries int , retryInterval time.Duration , authToken string , instanceAppID string , distribution string ) error {
565602
566603 for attempt := 1 ; attempt <= maxRetries ; attempt ++ {
567604 fmt .Printf ("Attempt %d/%d: Checking resource states...\n " , attempt , maxRetries )
568605
569- events , err := getEvents (ctx , authToken , appID )
606+ events , err := getEvents (ctx , authToken , instanceAppID )
570607 if err != nil {
571608 if attempt == maxRetries {
572609 return fmt .Errorf ("failed to get events after %d attempts: %w" , maxRetries , err )
@@ -606,6 +643,53 @@ func waitForResourcesReady(ctx context.Context, resources []Resource, maxRetries
606643 return fmt .Errorf ("unreachable code" )
607644}
608645
646+ // getRunningImages calls the vendor API to retrieve running images for the given instance and returns a set of image names.
647+ func getRunningImages (ctx context.Context , appID string , customerID string , instanceAppID string , authToken string ) (map [string ]struct {}, error ) {
648+ url := fmt .Sprintf ("https://api.replicated.com/vendor/v3/app/%s/customer/%s/instance/%s/running-images" , appID , customerID , instanceAppID )
649+
650+ req , err := http .NewRequestWithContext (ctx , "GET" , url , nil )
651+ if err != nil {
652+ return nil , fmt .Errorf ("failed to create request: %w" , err )
653+ }
654+ req .Header .Set ("Accept" , "application/json" )
655+ req .Header .Set ("Authorization" , authToken )
656+
657+ client := & http.Client {}
658+ resp , err := client .Do (req )
659+ if err != nil {
660+ return nil , fmt .Errorf ("failed to make request: %w" , err )
661+ }
662+ defer resp .Body .Close ()
663+
664+ if resp .StatusCode != http .StatusOK {
665+ body , _ := io .ReadAll (resp .Body )
666+ return nil , fmt .Errorf ("vendor API request failed with status %d: %s" , resp .StatusCode , string (body ))
667+ }
668+
669+ body , err := io .ReadAll (resp .Body )
670+ if err != nil {
671+ return nil , fmt .Errorf ("failed to read response: %w" , err )
672+ }
673+
674+ var response struct {
675+ RunningImages map [string ][]string `json:"running_images"`
676+ }
677+ if err := json .Unmarshal (body , & response ); err != nil {
678+ return nil , fmt .Errorf ("failed to unmarshal vendor API response: %w" , err )
679+ }
680+ if len (response .RunningImages ) == 0 {
681+ return nil , fmt .Errorf ("vendor API returned no running_images: %s" , string (body ))
682+ }
683+
684+ imagesSet := map [string ]struct {}{}
685+ for name := range response .RunningImages {
686+ if name != "" {
687+ imagesSet [name ] = struct {}{}
688+ }
689+ }
690+ return imagesSet , nil
691+ }
692+
609693// upgradeChartAndRestart upgrades the helm chart with the provided arguments and restarts deployments
610694func upgradeChartAndRestart (
611695 ctx context.Context ,
@@ -685,7 +769,7 @@ func upgradeChartAndRestart(
685769 []string {
686770 "kubectl" , "rollout" , "restart" , "deploy/test-chart" ,
687771 }))
688- out , err = ctr .Stdout (ctx )
772+ _ , err = ctr .Stdout (ctx )
689773 if err != nil {
690774 return fmt .Errorf ("failed to restart test-chart deployment: %w" , err )
691775 }
0 commit comments