github.com/pojntfx/hydrapp/hydrapp@v0.0.0-20240516002902-d08759d6ca9f/pkg/executors/docker.go (about)

     1  package executors
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/user"
     9  	"runtime"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/container"
    13  	"github.com/docker/docker/client"
    14  	cp "github.com/otiai10/copy"
    15  )
    16  
    17  func DockerRunImage(
    18  	ctx context.Context,
    19  	cli *client.Client,
    20  	image string,
    21  	pull bool,
    22  	privileged bool,
    23  	src string,
    24  	dst string,
    25  	onID func(id string),
    26  	stdout io.Writer,
    27  	env map[string]string,
    28  	renderTemplates func(workdir string, ejecting bool) error,
    29  	cmds []string,
    30  ) error {
    31  	currentUser, err := user.Current()
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	if runtime.GOOS != "windows" {
    37  		env["DST_UID"] = currentUser.Uid
    38  		env["DST_GID"] = currentUser.Gid
    39  	}
    40  
    41  	images, err := cli.ImageList(ctx, types.ImageListOptions{})
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	imageExists := false
    47  o:
    48  	for _, i := range images {
    49  		for _, t := range i.RepoTags {
    50  			if t == image {
    51  				imageExists = true
    52  
    53  				break o
    54  			}
    55  		}
    56  	}
    57  
    58  	if pull || !imageExists {
    59  		reader, err := cli.ImagePull(ctx, image, types.ImagePullOptions{})
    60  		if err != nil {
    61  			return err
    62  		}
    63  
    64  		if _, err := io.Copy(stdout, reader); err != nil {
    65  			return err
    66  		}
    67  	}
    68  
    69  	workdir, err := os.MkdirTemp("", "hydrapp-build-dir-")
    70  	if err != nil {
    71  		return err
    72  	}
    73  	defer os.RemoveAll(workdir)
    74  
    75  	if err := cp.Copy(src, workdir); err != nil {
    76  		return err
    77  	}
    78  
    79  	if err := renderTemplates(workdir, false); err != nil {
    80  		return err
    81  	}
    82  
    83  	var cmd []string
    84  	binds := []string{
    85  		workdir + ":/hydrapp/work:z",
    86  	}
    87  
    88  	if len(cmds) > 0 {
    89  		cmd = cmds
    90  	} else {
    91  		binds = append(binds, dst+":/hydrapp/dst:z")
    92  	}
    93  
    94  	resp, err := cli.ContainerCreate(ctx, &container.Config{
    95  		Image:        image,
    96  		AttachStdin:  true,
    97  		AttachStdout: true,
    98  		AttachStderr: true,
    99  		OpenStdin:    true,
   100  		Tty:          true,
   101  		Env: func() []string {
   102  			out := []string{}
   103  			for key, value := range env {
   104  				out = append(out, key+"="+value)
   105  			}
   106  
   107  			return out
   108  		}(),
   109  		Cmd: cmd,
   110  	}, &container.HostConfig{
   111  		Privileged: privileged,
   112  		Binds:      binds,
   113  	}, nil, nil, "")
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	onID(resp.ID)
   119  
   120  	waiter, err := cli.ContainerAttach(ctx, resp.ID, container.AttachOptions{
   121  		Stdin:  false,
   122  		Stdout: true,
   123  		Stderr: true,
   124  		Stream: true,
   125  	})
   126  	if err != nil {
   127  		return err
   128  	}
   129  	defer waiter.Close()
   130  
   131  	go io.Copy(stdout, waiter.Reader) // We intentionally ignore errors here since we can't handle them
   132  
   133  	if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
   134  		return err
   135  	}
   136  
   137  	statusChan, errChan := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
   138  	select {
   139  	case err := <-errChan:
   140  		return err
   141  	case status := <-statusChan:
   142  		if (status.StatusCode != 0 && status.StatusCode != 137) || status.Error != nil {
   143  			return fmt.Errorf("could not wait for container: %v", status)
   144  		}
   145  	}
   146  
   147  	return nil
   148  }