package jobrunner import ( "bytes" "context" "fmt" "io" "os" "os/exec" "path/filepath" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" "github.com/op/go-logging" ) var log = logging.MustGetLogger("cursorius-server") type Job struct { Id string URL string } func RunJob(job Job, workingDir string) error { jobFolder := filepath.Join(workingDir, job.Id) log.Debugf("Job %v configured with URL \"%v\"", job.Id, job.URL) log.Debugf("Job %v configured with folder \"%v\"", job.Id, jobFolder) err := os.RemoveAll(jobFolder) if err != nil { return fmt.Errorf("could not delete existing folder %v", jobFolder) } err = os.MkdirAll(jobFolder, 0755) if err != nil { return fmt.Errorf("could not create working directory for job %v: %v", job.Id, err) } log.Infof("Cloning source from URL %v", job.URL) cloneCmd := exec.Command("git", "clone", job.URL, jobFolder) output, err := cloneCmd.CombinedOutput() if err != nil { log.Debugf("%s", output) return fmt.Errorf("could not clone source: %v", err) } cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return fmt.Errorf("Could not create docker client: %v", err) } log.Info("Source cloned successfully") ctx := context.Background() imageName := "git.ohea.xyz/cursorius/cursorius:latest" log.Infof("Pulling image %v", imageName) pullOutput, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{}) if err != nil { return fmt.Errorf("could not pull image %v: %v", imageName, err) } buf, err := io.ReadAll(pullOutput) if err != nil { return fmt.Errorf("could not read from io.ReadCloser:, %v", err) } log.Infof("%s", buf) err = pullOutput.Close() if err != nil { return fmt.Errorf("could not close io.ReadCloser: %v", err) } log.Info("Image pulled sucessfully") resp, err := cli.ContainerCreate(ctx, &container.Config{ Image: imageName, Cmd: []string{"/launcher.sh"}, Tty: false, //Env: []string{fmt.Sprintf("CURSORIUS_SRC_DIR=%s", jobFolder)}, Env: []string{fmt.Sprintf("CURSORIUS_SRC_DIR=/cursorius/src")}, }, // TODO: fix running the runner in docker (add VolumesFrom to HostConfig) &container.HostConfig{ Mounts: []mount.Mount{ { Type: mount.TypeBind, Source: jobFolder, Target: "/cursorius/src", ReadOnly: false, Consistency: mount.ConsistencyDefault, }, { Type: mount.TypeBind, Source: "/var/run/docker.sock", Target: "/var/run/docker.sock", ReadOnly: false, Consistency: mount.ConsistencyDefault, }, }, }, nil, nil, "", ) if err != nil { return fmt.Errorf("could not create container: %v", err) } log.Info("Launching container") if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { return fmt.Errorf("could not start container: %v", err) } statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) select { case err := <-errCh: if err != nil { return fmt.Errorf("container returned error: %v", err) } case retCode := <-statusCh: log.Debugf("Container finished running with return code: %v", retCode) } out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) if err != nil { return fmt.Errorf("could not get container logs: %v", err) } var stdOut bytes.Buffer var stdErr bytes.Buffer stdcopy.StdCopy(&stdOut, &stdErr, out) log.Debugf("%s", stdOut.Bytes()) log.Debugf("%s", stdErr.Bytes()) return nil }