GitRoot

craft your forge, build your project, grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package exec
  6
  7import (
  8	"fmt"
  9	"os"
 10	"path/filepath"
 11	"strings"
 12
 13	"github.com/samber/oops"
 14	executorLib "gitroot.dev/executor/lib"
 15	pluginLib "gitroot.dev/libs/golang/plugin/model"
 16	"gitroot.dev/server/configuration"
 17	"gitroot.dev/server/logger"
 18)
 19
 20type Container struct {
 21	logger *logger.Logger
 22	conf   configuration.ContainerConf
 23}
 24
 25func NewContainer(log *logger.Logger, conf configuration.ContainerConf) *Container {
 26	mylog := log.NewSubLogger("container")
 27	return &Container{
 28		logger: mylog,
 29		conf:   conf,
 30	}
 31}
 32
 33func (e *Container) name() string {
 34	return "container"
 35}
 36
 37func (e *Container) prepareTempDir(dir string) error {
 38	if err := prepareJobRunner(dir); err != nil {
 39		return oops.Wrapf(err, "can't prepareJobRunner")
 40	}
 41
 42	return nil
 43}
 44
 45func (e *Container) exec(dir string, exe pluginLib.Exec, projectPluginCacheDir string) (*pluginLib.ExecStatus, error) {
 46	isRemoteImage := !strings.HasSuffix(exe.Build, "Containerfile") && !strings.HasSuffix(exe.Build, "Dockerfile")
 47	imageName := strings.ReplaceAll(strings.TrimLeft(dir, "/"), "/", "_")
 48	if exe.Build == "" {
 49		imageName = "docker.io/bash"
 50	} else if isRemoteImage {
 51		imageName = exe.Build
 52	}
 53
 54	envStr := make([]string, len(exe.Env))
 55	for i, env := range exe.Env {
 56		envStr[i] = fmt.Sprintf("-e %s", env)
 57	}
 58
 59	args := []string{
 60		"run", "--rm",
 61		"-v", "/var/log:/var/log", //TODO make configurable
 62		"-v", fmt.Sprintf("%s:/%s", filepath.Join(dir, APP_DIR), APP_DIR),
 63		"-v", fmt.Sprintf("%s:/%s", filepath.Join(dir, JOB_CONTEXT_DIR), JOB_CONTEXT_DIR),
 64	}
 65	args = append(args, envStr...)
 66	args = append(args, fmt.Sprintf("-w=/%s", APP_DIR))
 67	for _, cache := range exe.Cache {
 68		cacheSource := filepath.Join(projectPluginCacheDir, CACHE_DIR, cache.Key)
 69		os.MkdirAll(cacheSource, os.ModePerm)
 70		if cache.ReadOnly {
 71			args = append(args, "-v", fmt.Sprintf("%s:/%s:ro", cacheSource, cache.Path))
 72		} else {
 73			args = append(args, "-v", fmt.Sprintf("%s:/%s", cacheSource, cache.Path))
 74		}
 75	}
 76	args = append(args, imageName)
 77
 78	execs, err := cmdToExec(exe, false)
 79	if err != nil {
 80		return nil, err
 81	}
 82	args = append(args, execs...)
 83
 84	subCmd := pluginLib.Exec{
 85		Cmds: []pluginLib.Cmd{
 86			{Cmd: e.conf.Bin, Args: args},
 87		},
 88		Env:       exe.Env,
 89		Artifacts: exe.Artifacts,
 90		Cache:     []pluginLib.Cache{},
 91	}
 92	if !isRemoteImage {
 93		ctxDir, _ := filepath.Split(exe.Build)
 94		subCmd.Cmds = append([]pluginLib.Cmd{{Cmd: e.conf.Bin, Args: []string{"build", "-t", imageName, ctxDir}}}, subCmd.Cmds...)
 95	}
 96	execStatus, err := executorLib.Start(filepath.Join(dir, APP_DIR), subCmd, false)
 97	if err != nil {
 98		return nil, err
 99	}
100	logToParse := 0
101	if !isRemoteImage {
102		logToParse = 1
103	}
104	d, err := os.ReadFile(filepath.Join(dir, JOB_CONTEXT_DIR, execStatus.CmdsLogs[logToParse]))
105	if err != nil {
106		return nil, err
107	}
108	subExecStatus, err := parseJobLog(d)
109	if err != nil {
110		return nil, err
111	}
112	for i, es := range subExecStatus.CmdsExec {
113		execStatus.CmdsExec = append(execStatus.CmdsExec, es)
114		execStatus.CmdsStatus = append(execStatus.CmdsStatus, subExecStatus.CmdsStatus[i])
115		execStatus.CmdsLogs = append(execStatus.CmdsLogs, subExecStatus.CmdsLogs[i])
116		execStatus.CmdsStats = append(execStatus.CmdsStats, subExecStatus.CmdsStats[i])
117	}
118	execStatus.Artifacts = append(execStatus.Artifacts, subExecStatus.Artifacts...)
119	return execStatus, nil
120}