99 "fmt"
1010 "io"
1111 "os"
12- "os/exec"
1312 "path/filepath"
1413 "runtime"
1514 "strings"
@@ -24,6 +23,7 @@ import (
2423 "github.com/gruntwork-io/terragrunt/internal/experiment"
2524 "github.com/gruntwork-io/terragrunt/internal/github"
2625 "github.com/gruntwork-io/terragrunt/internal/os/signal"
26+ "github.com/gruntwork-io/terragrunt/internal/vexec"
2727
2828 "github.com/hashicorp/go-hclog"
2929
@@ -90,10 +90,13 @@ type engineInstance struct {
9090 execOptions * ExecutionOptions
9191}
9292
93- // Run executes the given command with the experimental engine.
93+ // Run executes the given command with the experimental engine. The provided
94+ // vexec.Exec is used to spawn the engine plugin subprocess and must be
95+ // OS-backed.
9496func Run (
9597 ctx context.Context ,
9698 l log.Logger ,
99+ e vexec.Exec ,
97100 execOptions * ExecutionOptions ,
98101) (* util.CmdOutput , error ) {
99102 engineClients , err := engineClientsFromContext (ctx )
@@ -110,7 +113,7 @@ func Run(
110113 return nil , errors .New (err )
111114 }
112115
113- terragruntEngine , client , createEngineErr := createEngine (ctx , l , execOptions )
116+ terragruntEngine , client , createEngineErr := createEngine (ctx , l , e , execOptions )
114117 if createEngineErr != nil {
115118 return nil , errors .New (createEngineErr )
116119 }
@@ -542,6 +545,7 @@ func logEngineMessage(l log.Logger, logLevel proto.LogLevel, content string) {
542545func createEngine (
543546 ctx context.Context ,
544547 l log.Logger ,
548+ e vexec.Exec ,
545549 execOptions * ExecutionOptions ,
546550) (* proto.EngineClient , * plugin.Client , error ) {
547551 if execOptions .EngineConfig == nil {
@@ -597,23 +601,27 @@ func createEngine(
597601 // We use without cancel here to ensure that the plugin isn't killed when the main context is cancelled,
598602 // like it is in the RunCommandWithOutput function. This ensures that we don't cancel the shutdown
599603 // when the command is cancelled.
600- cmd := exec .CommandContext (
601- context .WithoutCancel (ctx ),
602- localEnginePath ,
603- )
604- cmd .Cancel = func () error {
605- if cmd .Process == nil {
606- return nil
604+ cmd := e .Command (context .WithoutCancel (ctx ), localEnginePath )
605+ cmd .SetEnv ([]string {fmt .Sprintf ("%s=%s" , engineLogLevelEnv , engineLogLevel )})
606+ cmd .SetCancel (func () error {
607+ sig := signal .SignalFromContext (ctx )
608+ if sig == nil {
609+ sig = os .Kill
607610 }
608611
609- if sig := signal . SignalFromContext ( ctx ); sig != nil {
610- return cmd . Process . Signal ( sig )
612+ if err := cmd . Signal ( sig ); err != nil && ! errors . Is ( err , vexec . ErrProcessNotStarted ) {
613+ return err
611614 }
612615
613- return cmd .Process .Signal (os .Kill )
616+ return nil
617+ })
618+
619+ // hashicorp/go-plugin's ClientConfig requires a concrete *exec.Cmd.
620+ osCmder , ok := cmd .(vexec.OSCmder )
621+ if ! ok {
622+ return nil , nil , errors .Errorf ("engine plugin spawn: %w" , vexec .ErrNotOSBacked )
614623 }
615- // pass log level to engine
616- cmd .Env = append (cmd .Env , fmt .Sprintf ("%s=%s" , engineLogLevelEnv , engineLogLevel ))
624+
617625 client := plugin .NewClient (& plugin.ClientConfig {
618626 Logger : logger ,
619627 HandshakeConfig : plugin.HandshakeConfig {
@@ -624,7 +632,7 @@ func createEngine(
624632 Plugins : map [string ]plugin.Plugin {
625633 "plugin" : & engine.TerragruntGRPCEngine {},
626634 },
627- Cmd : cmd ,
635+ Cmd : osCmder . OSCmd () ,
628636 GRPCDialOptions : []grpc.DialOption {
629637 grpc .WithTransportCredentials (insecure .NewCredentials ()),
630638 },
0 commit comments