@@ -507,6 +507,14 @@ func TestQEMUBasicEndToEnd(t *testing.T) {
507507 t .Logf ("Volume test output:\n %s" , output )
508508 t .Log ("Volume read/write test passed!" )
509509
510+ // Test environment variables are accessible via exec (tests guest-agent has env vars)
511+ t .Log ("Testing environment variables via exec..." )
512+ output , exitCode , err = runCmd ("printenv" , "TEST_VAR" )
513+ require .NoError (t , err , "printenv should execute" )
514+ require .Equal (t , 0 , exitCode , "printenv should succeed" )
515+ assert .Equal (t , "test_value" , strings .TrimSpace (output ), "Environment variable should be accessible via exec" )
516+ t .Log ("Environment variable accessible via exec!" )
517+
510518 // Delete instance
511519 t .Log ("Deleting instance..." )
512520 err = manager .DeleteInstance (ctx , inst .Id )
@@ -537,6 +545,175 @@ func TestQEMUBasicEndToEnd(t *testing.T) {
537545 t .Log ("QEMU instance lifecycle test complete!" )
538546}
539547
548+ // TestQEMUEntrypointEnvVars verifies that environment variables are passed to the entrypoint process.
549+ // This uses bitnami/redis which configures REDIS_PASSWORD from an env var - if auth is required,
550+ // it proves the entrypoint received and used the env var.
551+ func TestQEMUEntrypointEnvVars (t * testing.T ) {
552+ if os .Getuid () != 0 {
553+ t .Skip ("Skipping test that requires root" )
554+ }
555+
556+ // Require QEMU to be installed
557+ starter := qemu .NewStarter ()
558+ if _ , err := starter .GetBinaryPath (nil , "" ); err != nil {
559+ t .Fatalf ("QEMU not available: %v" , err )
560+ }
561+
562+ mgr , tmpDir := setupTestManagerForQEMU (t )
563+ ctx := context .Background ()
564+
565+ // Get image manager
566+ p := paths .New (tmpDir )
567+ imageManager , err := images .NewManager (p , 1 , nil )
568+ require .NoError (t , err )
569+
570+ // Pull bitnami/redis image
571+ t .Log ("Pulling bitnami/redis image..." )
572+ redisImage , err := imageManager .CreateImage (ctx , images.CreateImageRequest {
573+ Name : "docker.io/bitnami/redis:latest" ,
574+ })
575+ require .NoError (t , err )
576+
577+ // Wait for image to be ready
578+ t .Log ("Waiting for image build to complete..." )
579+ imageName := redisImage .Name
580+ for i := 0 ; i < 60 ; i ++ {
581+ img , err := imageManager .GetImage (ctx , imageName )
582+ if err == nil && img .Status == images .StatusReady {
583+ redisImage = img
584+ break
585+ }
586+ if err == nil && img .Status == images .StatusFailed {
587+ t .Fatalf ("Image build failed: %s" , * img .Error )
588+ }
589+ time .Sleep (1 * time .Second )
590+ }
591+ require .Equal (t , images .StatusReady , redisImage .Status , "Image should be ready after 60 seconds" )
592+ t .Log ("Redis image ready" )
593+
594+ // Ensure system files
595+ systemManager := system .NewManager (p )
596+ t .Log ("Ensuring system files..." )
597+ err = systemManager .EnsureSystemFiles (ctx )
598+ require .NoError (t , err )
599+ t .Log ("System files ready" )
600+
601+ // Initialize network (needed for loopback interface in guest)
602+ networkManager := network .NewManager (p , & config.Config {
603+ DataDir : tmpDir ,
604+ BridgeName : "vmbr0" ,
605+ SubnetCIDR : "10.100.0.0/16" ,
606+ DNSServer : "1.1.1.1" ,
607+ }, nil )
608+ t .Log ("Initializing network..." )
609+ err = networkManager .Initialize (ctx , nil )
610+ require .NoError (t , err )
611+ t .Log ("Network initialized" )
612+
613+ // Create instance with REDIS_PASSWORD env var
614+ testPassword := "test_secret_password_123"
615+ req := CreateInstanceRequest {
616+ Name : "test-redis-env" ,
617+ Image : "docker.io/bitnami/redis:latest" ,
618+ Size : 2 * 1024 * 1024 * 1024 ,
619+ HotplugSize : 512 * 1024 * 1024 ,
620+ OverlaySize : 10 * 1024 * 1024 * 1024 ,
621+ Vcpus : 2 ,
622+ NetworkEnabled : true , // Need network for loopback to work properly
623+ Env : map [string ]string {
624+ "REDIS_PASSWORD" : testPassword ,
625+ },
626+ }
627+
628+ t .Log ("Creating redis instance with REDIS_PASSWORD..." )
629+ inst , err := mgr .CreateInstance (ctx , req )
630+ require .NoError (t , err )
631+ require .NotNil (t , inst )
632+ assert .Equal (t , StateRunning , inst .State )
633+ assert .Equal (t , hypervisor .TypeQEMU , inst .HypervisorType , "Instance should use QEMU hypervisor" )
634+ t .Logf ("Instance created: %s" , inst .Id )
635+
636+ // Wait for redis to be ready (bitnami/redis takes longer to start)
637+ t .Log ("Waiting for redis to be ready..." )
638+ time .Sleep (15 * time .Second )
639+
640+ // Helper to run command in guest with retry
641+ runCmd := func (command ... string ) (string , int , error ) {
642+ var lastOutput string
643+ var lastExitCode int
644+ var lastErr error
645+
646+ dialer , err := hypervisor .NewVsockDialer (inst .HypervisorType , inst .VsockSocket , inst .VsockCID )
647+ if err != nil {
648+ return "" , - 1 , err
649+ }
650+
651+ for attempt := 0 ; attempt < 5 ; attempt ++ {
652+ if attempt > 0 {
653+ time .Sleep (200 * time .Millisecond )
654+ }
655+
656+ var stdout , stderr bytes.Buffer
657+ exit , err := guest .ExecIntoInstance (ctx , dialer , guest.ExecOptions {
658+ Command : command ,
659+ Stdout : & stdout ,
660+ Stderr : & stderr ,
661+ TTY : false ,
662+ })
663+
664+ output := stdout .String ()
665+ if stderr .Len () > 0 {
666+ output += stderr .String ()
667+ }
668+ output = strings .TrimSpace (output )
669+
670+ if err != nil {
671+ lastErr = err
672+ lastOutput = output
673+ lastExitCode = - 1
674+ continue
675+ }
676+
677+ lastOutput = output
678+ lastExitCode = exit .Code
679+ lastErr = nil
680+
681+ if output != "" || exit .Code == 0 {
682+ return output , exit .Code , nil
683+ }
684+ }
685+
686+ return lastOutput , lastExitCode , lastErr
687+ }
688+
689+ // Test 1: PING without auth should fail
690+ t .Log ("Testing redis PING without auth (should fail)..." )
691+ output , _ , err := runCmd ("redis-cli" , "PING" )
692+ require .NoError (t , err )
693+ assert .Contains (t , output , "NOAUTH" , "Redis should require authentication" )
694+
695+ // Test 2: PING with correct password should succeed
696+ t .Log ("Testing redis PING with correct password (should succeed)..." )
697+ output , exitCode , err := runCmd ("redis-cli" , "-a" , testPassword , "PING" )
698+ require .NoError (t , err )
699+ require .Equal (t , 0 , exitCode )
700+ assert .Contains (t , output , "PONG" , "Redis should respond to authenticated PING" )
701+
702+ // Test 3: Verify requirepass config matches our env var
703+ t .Log ("Verifying redis requirepass config..." )
704+ output , exitCode , err = runCmd ("redis-cli" , "-a" , testPassword , "CONFIG" , "GET" , "requirepass" )
705+ require .NoError (t , err )
706+ require .Equal (t , 0 , exitCode )
707+ assert .Contains (t , output , testPassword , "Redis requirepass should match REDIS_PASSWORD env var" )
708+
709+ t .Log ("QEMU entrypoint environment variable test passed!" )
710+
711+ // Cleanup
712+ t .Log ("Cleaning up..." )
713+ err = mgr .DeleteInstance (ctx , inst .Id )
714+ require .NoError (t , err )
715+ }
716+
540717// TestQEMUStandbyAndRestore tests the standby/restore cycle with QEMU.
541718// This tests QEMU's migrate-to-file snapshot mechanism.
542719func TestQEMUStandbyAndRestore (t * testing.T ) {
0 commit comments