github.com/defang-io/defang/src@v0.0.0-20240505002154-bdf411911834/pkg/local/local.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package local
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"io"
    10  	"os"
    11  	"os/exec"
    12  	"strconv"
    13  	"syscall"
    14  
    15  	"github.com/defang-io/defang/src/pkg/types"
    16  )
    17  
    18  type PID = types.TaskID
    19  
    20  type Local struct {
    21  	entrypoint []string
    22  	cmd        *exec.Cmd
    23  	outReader  io.ReadCloser
    24  	errReader  io.ReadCloser
    25  }
    26  
    27  var _ types.Driver = (*Local)(nil)
    28  
    29  func New() *Local {
    30  	return &Local{}
    31  }
    32  
    33  func (l *Local) SetUp(ctx context.Context, containers []types.Container) error {
    34  	if len(containers) != 1 {
    35  		return errors.New("expected exactly one container")
    36  	}
    37  	if len(containers[0].EntryPoint) == 0 {
    38  		return errors.New("entrypoint not set")
    39  	}
    40  	l.entrypoint = containers[0].EntryPoint
    41  	return nil
    42  }
    43  
    44  func (l *Local) TearDown(ctx context.Context) error {
    45  	if l.cmd == nil {
    46  		return nil
    47  	}
    48  	// l.cmd.Process.Kill()
    49  	return l.cmd.Wait()
    50  }
    51  
    52  func (l *Local) Run(ctx context.Context, env map[string]string, args ...string) (PID, error) {
    53  	if l.cmd != nil {
    54  		return nil, errors.New("already running")
    55  	}
    56  	args = append(l.entrypoint[1:], args...)
    57  	cmd := exec.CommandContext(ctx, l.entrypoint[0], args...)
    58  	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    59  	or, err := cmd.StdoutPipe()
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	er, err := cmd.StderrPipe()
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	for k, v := range env {
    68  		cmd.Env = append(cmd.Env, k+"="+v)
    69  	}
    70  	if err := cmd.Start(); err != nil {
    71  		return nil, err
    72  	}
    73  	l.outReader = or
    74  	l.errReader = er
    75  	l.cmd = cmd
    76  	pid := strconv.Itoa(cmd.Process.Pid)
    77  	return &pid, nil
    78  }
    79  
    80  func (l *Local) Tail(ctx context.Context, taskID PID) error {
    81  	if l.cmd == nil {
    82  		return errors.New("not running")
    83  	}
    84  	if strconv.Itoa(l.cmd.Process.Pid) != *taskID {
    85  		return errors.New("task ID does not match")
    86  	}
    87  	go io.Copy(os.Stderr, l.errReader)
    88  	_, err := io.Copy(os.Stdout, l.outReader)
    89  	return err
    90  }
    91  
    92  func (l *Local) Stop(ctx context.Context, taskID PID) error {
    93  	pid, err := strconv.Atoi(*taskID)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	return syscall.Kill(-pid, syscall.SIGTERM) // negative pid kills the process group
    98  }
    99  
   100  func (l *Local) GetInfo(ctx context.Context, taskID PID) (*types.TaskInfo, error) {
   101  	return nil, errors.New("not implemented for local driver")
   102  }
   103  
   104  func (l *Local) PutSecret(ctx context.Context, name, value string) error {
   105  	return errors.New("not implemented for local driver")
   106  }
   107  
   108  func (l *Local) ListSecrets(ctx context.Context) ([]string, error) {
   109  	return nil, errors.New("not implemented for local driver")
   110  }
   111  
   112  func (l *Local) CreateUploadURL(ctx context.Context, name string) (string, error) {
   113  	return "", errors.New("not implemented for local driver")
   114  }