From 79734057c14f620a152825ebd8a842748e65e38a Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Mon, 23 Dec 2024 18:25:36 +0400 Subject: [PATCH] Support Linux (#97) * install-gitlab-runner*.sh: dynamically detect OS and architecture * Introduce (*VM).Info() method * Handle builds and cache directories in a cross-platform fashion * Explain "/var/tmp" --- internal/commands/config/config.go | 22 +++++---- .../prepare/install-gitlab-runner-auto.sh | 13 +++++- .../prepare/install-gitlab-runner-curl.sh | 13 +++++- internal/commands/prepare/prepare.go | 46 ++++++++++++++----- internal/tart/config.go | 10 ++++ internal/tart/vm.go | 33 ++++++++++--- 6 files changed, 108 insertions(+), 29 deletions(-) diff --git a/internal/commands/config/config.go b/internal/commands/config/config.go index fb0ce67..06862ab 100644 --- a/internal/commands/config/config.go +++ b/internal/commands/config/config.go @@ -53,12 +53,15 @@ func runConfig(cmd *cobra.Command, args []string) error { // to be absolute[1]. // // 2. GitLab Runner uses relative paths internally which results in improper directory traversal[2], - // this is why we use "/private/tmp" instead of just "/tmp" here as a workaround. + // so instead of "/tmp" we need to use "/private/tmp" here as a workaround. + // + // 3. However, there's no "/private/tmp" on Linux. So we use the lowest common denominator + // in the form of "/var/tmp". It's both (1) not a symbolic link and (2) is present on both platforms. // // [1]: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section // [2]: https://gitlab.com/gitlab-org/gitlab-runner/-/issues/31003 - BuildsDir: "/private/tmp/builds", - CacheDir: "/private/tmp/cache", + BuildsDir: "/var/tmp/builds", + CacheDir: "/var/tmp/cache", JobEnv: map[string]string{}, } @@ -95,15 +98,14 @@ func runConfig(cmd *cobra.Command, args []string) error { // Figure out the builds directory override to use switch { case tartConfig.HostDir: - gitlabRunnerConfig.BuildsDir = fmt.Sprintf("/Users/%s/hostdir", tartConfig.SSHUsername) + gitlabRunnerConfig.JobEnv[tart.EnvTartExecutorInternalBuildsDirOnHost] = gitLabEnv.HostDirPath() if err := os.MkdirAll(gitLabEnv.HostDirPath(), 0700); err != nil { return err } case buildsDir != "": - gitlabRunnerConfig.BuildsDir = fmt.Sprintf("/Users/%s/buildsdir", tartConfig.SSHUsername) buildsDir = os.ExpandEnv(buildsDir) - gitlabRunnerConfig.JobEnv[tart.EnvTartExecutorInternalBuildsDir] = buildsDir + gitlabRunnerConfig.JobEnv[tart.EnvTartExecutorInternalBuildsDirOnHost] = buildsDir if err := os.MkdirAll(buildsDir, 0700); err != nil { return err @@ -115,9 +117,8 @@ func runConfig(cmd *cobra.Command, args []string) error { // Figure out the cache directory override to use switch { case cacheDir != "": - gitlabRunnerConfig.CacheDir = fmt.Sprintf("/Users/%s/cachedir", tartConfig.SSHUsername) cacheDir = os.ExpandEnv(cacheDir) - gitlabRunnerConfig.JobEnv[tart.EnvTartExecutorInternalCacheDir] = cacheDir + gitlabRunnerConfig.JobEnv[tart.EnvTartExecutorInternalCacheDirOnHost] = cacheDir if err := os.MkdirAll(cacheDir, 0700); err != nil { return err @@ -126,6 +127,11 @@ func runConfig(cmd *cobra.Command, args []string) error { gitlabRunnerConfig.CacheDir = guestCacheDir } + // Propagate builds and cache directory locations in the guest + // because GitLab Runner won't do this for us + gitlabRunnerConfig.JobEnv[tart.EnvTartExecutorInternalBuildsDir] = gitlabRunnerConfig.BuildsDir + gitlabRunnerConfig.JobEnv[tart.EnvTartExecutorInternalCacheDir] = gitlabRunnerConfig.CacheDir + jsonBytes, err := json.MarshalIndent(&gitlabRunnerConfig, "", " ") if err != nil { return err diff --git a/internal/commands/prepare/install-gitlab-runner-auto.sh b/internal/commands/prepare/install-gitlab-runner-auto.sh index 06a00b8..0a7de24 100644 --- a/internal/commands/prepare/install-gitlab-runner-auto.sh +++ b/internal/commands/prepare/install-gitlab-runner-auto.sh @@ -10,7 +10,18 @@ # set -euo pipefail -GITLAB_RUNNER_URL="https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-arm64" +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) +case $ARCH in + aarch64) + ARCH="arm64" + ;; + x86_64) + ARCH="amd64" + ;; +esac + +GITLAB_RUNNER_URL="https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-${OS}-${ARCH}" GITLAB_RUNNER_PATH="/usr/local/bin/gitlab-runner" # Is GitLab Runner already installed? diff --git a/internal/commands/prepare/install-gitlab-runner-curl.sh b/internal/commands/prepare/install-gitlab-runner-curl.sh index 8f0833f..a9e6192 100644 --- a/internal/commands/prepare/install-gitlab-runner-curl.sh +++ b/internal/commands/prepare/install-gitlab-runner-curl.sh @@ -10,8 +10,19 @@ # set -euo pipefail +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) +case $ARCH in + aarch64) + ARCH="arm64" + ;; + x86_64) + ARCH="amd64" + ;; +esac + GITLAB_RUNNER_VERSION="latest" -GITLAB_RUNNER_URL="https://gitlab-runner-downloads.s3.amazonaws.com/${GITLAB_RUNNER_VERSION}/binaries/gitlab-runner-darwin-arm64" +GITLAB_RUNNER_URL="https://gitlab-runner-downloads.s3.amazonaws.com/${GITLAB_RUNNER_VERSION}/binaries/gitlab-runner-${OS}-${ARCH}" GITLAB_RUNNER_PATH="/usr/local/bin/gitlab-runner" # Is GitLab Runner already installed? diff --git a/internal/commands/prepare/prepare.go b/internal/commands/prepare/prepare.go index 5da464a..be9fb2f 100644 --- a/internal/commands/prepare/prepare.go +++ b/internal/commands/prepare/prepare.go @@ -191,19 +191,33 @@ func runPrepareVM(cmd *cobra.Command, args []string) error { log.Printf("Timezone was set to %s!\n", tz) } - dirsToMount := []string{} - if config.HostDir { - dirsToMount = append(dirsToMount, "hostdir") + type MountPoint struct { + Name string + Path string } - if _, ok := os.LookupEnv(tart.EnvTartExecutorInternalBuildsDir); ok { - dirsToMount = append(dirsToMount, "buildsdir") + + vmInfo, err := vm.Info(cmd.Context()) + if err != nil { + return err } - if _, ok := os.LookupEnv(tart.EnvTartExecutorInternalCacheDir); ok { - dirsToMount = append(dirsToMount, "cachedir") + + var mountPoints []MountPoint + + if _, ok := os.LookupEnv(tart.EnvTartExecutorInternalBuildsDirOnHost); ok { + mountPoints = append(mountPoints, MountPoint{ + Name: "buildsdir", + Path: os.Getenv(tart.EnvTartExecutorInternalBuildsDir), + }) + } + if _, ok := os.LookupEnv(tart.EnvTartExecutorInternalCacheDirOnHost); ok { + mountPoints = append(mountPoints, MountPoint{ + Name: "cachedir", + Path: os.Getenv(tart.EnvTartExecutorInternalCacheDir), + }) } - for _, dirToMount := range dirsToMount { - log.Printf("Mounting %s...\n", dirToMount) + for _, mountPoint := range mountPoints { + log.Printf("Mounting %s on %s...\n", mountPoint.Name, mountPoint.Path) session, err := ssh.NewSession() if err != nil { @@ -211,9 +225,17 @@ func runPrepareVM(cmd *cobra.Command, args []string) error { } defer session.Close() - mountPoint := fmt.Sprintf("/Users/%s/%s", config.SSHUsername, dirToMount) - mkdirScript := fmt.Sprintf("mkdir -p %s", mountPoint) - mountScript := fmt.Sprintf("mount_virtiofs tart.virtiofs.%s.%s %s", dirToMount, gitLabEnv.JobID, mountPoint) + var command string + + if vmInfo.OS == "darwin" { + command = "mount_virtiofs" + } else { + command = "sudo mount -t virtiofs" + } + + mkdirScript := fmt.Sprintf("mkdir -p %s", mountPoint.Path) + mountScript := fmt.Sprintf("%s tart.virtiofs.%s.%s %s", command, mountPoint.Name, + gitLabEnv.JobID, mountPoint.Path) session.Stdin = bytes.NewBufferString(strings.Join([]string{mkdirScript, mountScript, ""}, "\n")) session.Stdout = os.Stdout session.Stderr = os.Stderr diff --git a/internal/tart/config.go b/internal/tart/config.go index dec76f6..ac7607c 100644 --- a/internal/tart/config.go +++ b/internal/tart/config.go @@ -24,10 +24,20 @@ const ( // by the user. EnvTartExecutorInternalBuildsDir = "TART_EXECUTOR_INTERNAL_BUILDS_DIR" + // EnvTartExecutorInternalBuildsDirOnHost is an internal environment variable + // that does not use the "CUSTOM_ENV_" prefix, thus preventing the override + // by the user. + EnvTartExecutorInternalBuildsDirOnHost = "TART_EXECUTOR_INTERNAL_BUILDS_DIR_ON_HOST" + // EnvTartExecutorInternalCacheDir is an internal environment variable // that does not use the "CUSTOM_ENV_" prefix, thus preventing the override // by the user. EnvTartExecutorInternalCacheDir = "TART_EXECUTOR_INTERNAL_CACHE_DIR" + + // EnvTartExecutorInternalCacheDirOnHost is an internal environment variable + // that does not use the "CUSTOM_ENV_" prefix, thus preventing the override + // by the user. + EnvTartExecutorInternalCacheDirOnHost = "TART_EXECUTOR_INTERNAL_CACHE_DIR_ON_HOST" ) type Config struct { diff --git a/internal/tart/vm.go b/internal/tart/vm.go index b07e988..50d73a8 100644 --- a/internal/tart/vm.go +++ b/internal/tart/vm.go @@ -3,6 +3,7 @@ package tart import ( "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -35,6 +36,10 @@ type VM struct { id string } +type VMInfo struct { + OS string `json:"os"` +} + func ExistingVM(gitLabEnv gitlab.Env) *VM { return &VM{ id: gitLabEnv.VirtualMachineID(), @@ -143,15 +148,14 @@ func (vm *VM) Start( runArgs = append(runArgs, "--disk", customDiskMount) } - if config.HostDir { - hostDir := gitLabEnv.HostDirPath() - runArgs = append(runArgs, "--dir", fmt.Sprintf("%s:tag=tart.virtiofs.hostdir.%s", hostDir, gitLabEnv.JobID)) - } else if buildsDir, ok := os.LookupEnv(EnvTartExecutorInternalBuildsDir); ok { - runArgs = append(runArgs, "--dir", fmt.Sprintf("%s:tag=tart.virtiofs.buildsdir.%s", buildsDir, gitLabEnv.JobID)) + if buildsDir, ok := os.LookupEnv(EnvTartExecutorInternalBuildsDirOnHost); ok { + runArgs = append(runArgs, "--dir", fmt.Sprintf("%s:tag=tart.virtiofs.buildsdir.%s", + buildsDir, gitLabEnv.JobID)) } - if cacheDir, ok := os.LookupEnv(EnvTartExecutorInternalCacheDir); ok { - runArgs = append(runArgs, "--dir", fmt.Sprintf("%s:tag=tart.virtiofs.cachedir.%s", cacheDir, gitLabEnv.JobID)) + if cacheDir, ok := os.LookupEnv(EnvTartExecutorInternalCacheDirOnHost); ok { + runArgs = append(runArgs, "--dir", fmt.Sprintf("%s:tag=tart.virtiofs.cachedir.%s", + cacheDir, gitLabEnv.JobID)) } runArgs = append(runArgs, vm.id) @@ -278,6 +282,21 @@ func (vm *VM) IP(ctx context.Context, config Config) (string, error) { return strings.TrimSpace(stdout), nil } +func (vm *VM) Info(ctx context.Context) (*VMInfo, error) { + stdout, _, err := TartExec(ctx, "get", "--format", "json", vm.id) + if err != nil { + return nil, err + } + + var vmInfo VMInfo + + if err := json.Unmarshal([]byte(stdout), &vmInfo); err != nil { + return nil, err + } + + return &vmInfo, nil +} + func (vm *VM) Stop() error { _, _, err := TartExec(context.Background(), "stop", vm.id)