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}