Skip to content

Commit

Permalink
feat: Refactor the mounting options for containerized runs ⚓ (#149)
Browse files Browse the repository at this point in the history
- Mounting options for containerized runs can now be adjusted better for container-in-container scenarios. 
  Feature is in testing phase and will be documented once ready.
  • Loading branch information
gabyx authored Mar 28, 2024
1 parent 9250172 commit 688ac3c
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 114 deletions.
2 changes: 1 addition & 1 deletion githooks/cmd/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func buildFromSource(
branch string,
commitSHA string) updates.Binaries {

log.Info("Building binaries from source at commit '%s'.", commitSHA)
log.InfoF("Building binaries from source at commit '%s'.", commitSHA)

// Clone another copy of the release clone into temporary directory
log.InfoF("Clone to temporary build directory '%s'", tempDir)
Expand Down
45 changes: 23 additions & 22 deletions githooks/container/container-in-container.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
package container

// EnvVariableContainerWorkspaceHostPath specifies the source mount path (can be a container volume too)
// on the host machine where the Git repository is located in which Githooks works on.
// Normally Githooks uses a bind mount, but for docker-in-docker that does not work
// then we need these variables.
// Example: `~/work/myproject`.
const EnvVariableContainerWorkspaceHostPath = "GITHOOKS_CONTAINER_WORKSPACE_HOST_PATH"

// EnvVariableContainerWorkspaceBasePath specifies a relative path to the host path
// above. Normally empty.
// The variable can contain `${repository-dir-name}` which is replaced by
// the current base name of the repository where Githooks runs.
// Example: `repos/${repository-dir-name}` (Git repo relative to `EnvVariableContainerWorkspaceHostPath`).
const EnvVariableContainerWorkspaceBasePath = "GITHOOKS_CONTAINER_WORKSPACE_BASE_PATH"

// EnvVariableContainerSharedHostPath specifies the source mount path (can be a container volume too)
// on the host machine where the shared hook repositories are located.
// Normally Githooks uses a bind mount, but for docker-in-docker that does not work
// and this variable must be set if shared hooks are needed.
// It makes sense to mount the host `~/.githooks/shared` path directly into
// container at the same place such that they are in sync with what the containerized hooks
// when this variable is set to e.g. `~/.githooks/shared`.
const EnvVariableContainerSharedHostPath = "GITHOOKS_CONTAINER_SHARED_HOST_PATH"
// EnvVariableContainerRunConfigFile is the YAML file which is used
// for all run invocations
// of containerized hooks. Its enables :
// - to set custom additional arguments to the container run invocation, e.g.
// mount special volumes or set special environment variables needed in CI.
// - override workspace path (`/mnt/workspace`) in the container.
// - override shared repository path `/mnt/shared` in the container.
//
// For example a file:
//
// ```yaml
//
// version: 1
// workspace-dir-dest: /builds/a/b/c
// shared-dir-dest: /builds/.githooks/shared
// auto-mount-workspace: false
// auto-mount-shared: false
// args: [ "--volumes-from", "123455" ]
//
// ```
// Would mount the volumes from container `123455` (podman) and
// use the workspace dir `builds/a/b/c` and `/builds/.githooks/shared`.
const EnvVariableContainerRunConfigFile = "GITHOOKS_CONTAINER_RUN_CONFIG_FILE"
84 changes: 84 additions & 0 deletions githooks/container/container-run-config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package container

import (
"os"

cm "github.com/gabyx/githooks/githooks/common"
)

// containerRunConfig is the format of the container run arguments file.
type containerRunConfig struct {
// The path in the container where the workspace dir is located.
// Defaults to `/mnt/workspace`.
WorkspacePathDest string `yaml:"workspace-path-dest"`

// If the workspace directory is automatically mounted or
// you do it yourself with `Args`. Defaults to `true`.
// Giving you the chance to mount it differently,
// e.g. `--volumes-from other-container` when you do not
// know the host path because you are already inside a container.
AutoMountWorkspace bool `yaml:"auto-mount-workspace"`

// The path in the container to the directory where all shared repositories
// are located.
// Defaults to `/mnt/shared`.
SharedPathDest string `yaml:"shared-path-dest"`

// If the shared directory is automatically mounted or
// you do it yourself `Args`.
// Giving you the chance to mount it differently,
// e.g. `--volumes-from other-container` when you do not
// know the host path because you are already inside a container.
// Defaults to `true`.
AutoMountShared bool `yaml:"auto-mount-shared"`

// Additional arguments to the container run command.
Args []string `yaml:"args"`

// The version of this file format.
Version int `yaml:"version"`
}

// Version for containerRunConfig.
// Version 1: Initial.
const containerRunConfigVersion int = 1

func createContainerRunConfig() containerRunConfig {
return containerRunConfig{
WorkspacePathDest: "/mnt/workspace",
AutoMountWorkspace: true,

SharedPathDest: "/mnt/shared",
AutoMountShared: true,

Version: containerRunConfigVersion,
}
}

func loadContainerRunConfig() (config containerRunConfig, err error) {
config = createContainerRunConfig()
file, exists := os.LookupEnv(EnvVariableContainerRunConfigFile)

if exists && cm.IsFile(file) {

err = cm.LoadYAML(file, &config)
if err != nil {
err = cm.CombineErrors(err, cm.ErrorF("Could not load file '%s'", file))

return
}

if config.Version < 0 || config.Version > containerRunConfigVersion {
err = cm.ErrorF(
"File '%s' has version '%v'. "+
"This version of Githooks only supports version >= 1 and <= '%v'.",
file,
config.Version,
containerRunConfigVersion)

return
}
}

return config, nil
}
1 change: 0 additions & 1 deletion githooks/container/executable.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
// ContainerizedExecutable contains the data to a script/executable file.
type ContainerizedExecutable struct {
containerType ContainerManagerType
usedVolumes bool

Cmd string // The command.

Expand Down
64 changes: 33 additions & 31 deletions githooks/container/manager-docker.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package container

import (
"os"
"os/exec"
"os/user"
"path"
Expand All @@ -26,6 +25,8 @@ type ManagerDocker struct {

// Only used to wrap podman into this structure as well.
mgrType ContainerManagerType

runConfig containerRunConfig
}

// ImagePull pulls an image with reference `ref`.
Expand Down Expand Up @@ -75,10 +76,6 @@ func (m *ManagerDocker) ImageRemove(ref string) (err error) {
return m.cmdCtx.Check("image", "rm", ref)
}

func resolveWSBasePath(envValue string, dirname string) string {
return strings.ReplaceAll(envValue, "${repository-dir-name}", dirname)
}

// NewHookRunExec runs a hook over a container.
func (m *ManagerDocker) NewHookRunExec(
ref string,
Expand All @@ -98,41 +95,30 @@ func (m *ManagerDocker) NewHookRunExec(

// Mount: Working directory.
// The repository where the hook runs.
// ==================================
mntWSSrc := workspaceDir
mntWSDest := "/mnt/workspace"
mntWSSharedSrc := path.Dir(workspaceHookDir)
mntWSSharedDest := "/mnt/shared"
mntWSDest := m.runConfig.WorkspacePathDest
// =================================

if hostPath := os.Getenv(EnvVariableContainerWorkspaceHostPath); strs.IsNotEmpty(hostPath) {
containerExec.usedVolumes = true
mntWSSrc = hostPath
}
// Mount: Shared hook directory.
// The directory which contain all shared hooks repositories.
// =================================
mntWSSharedSrc := path.Dir(workspaceHookDir)
mntWSSharedDest := m.runConfig.SharedPathDest
// =================================

workingDir := path.Join(mntWSDest,
resolveWSBasePath(
os.Getenv(EnvVariableContainerWorkspaceBasePath),
path.Base(workspaceDir),
)) // defaults to `mntWSDest`
workingDir := mntWSDest

// Mount: Shared hook repository:
// This mount contains a shared repository root directory.
var cmdBasePath string
// only mount shared if we need them (we are running shared hooks)
mountWSShared := workspaceDir != workspaceHookDir

if !mountWSShared {
// Hooks are configured in current repository: Dont mount the shared location.
cmdBasePath = mntWSDest
} else {
// Mount shared too.
if hostPath := os.Getenv(EnvVariableContainerSharedHostPath); strs.IsNotEmpty(hostPath) {
mntWSSharedSrc = hostPath
} else if containerExec.usedVolumes {
return nil, cm.ErrorF(
"Host path for workspace '%s' set but missing a host path "+
"for shared hooks to run containerized. "+
"See the Githooks manual to configure it.", mntWSSrc)
}

cmdBasePath = path.Join(mntWSSharedDest, path.Base(workspaceHookDir))
}

Expand Down Expand Up @@ -160,9 +146,7 @@ func (m *ManagerDocker) NewHookRunExec(
containerExec.ArgsPre = []string{
"run",
"--rm",
"-v",
strs.Fmt("%v:%v", mntWSSrc, mntWSDest), // Set the mount for the working directory.
"-w", workingDir, // Set working dir.
"-w", workingDir, // Set working dir.
}

if attachStdIn {
Expand All @@ -173,12 +157,24 @@ func (m *ManagerDocker) NewHookRunExec(
containerExec.ArgsPre = append(containerExec.ArgsPre, "--tty")
}

if mountWSShared {
// Mount the workspace directory if set.
if m.runConfig.AutoMountWorkspace {
containerExec.ArgsPre = append(containerExec.ArgsPre,
"-v",
strs.Fmt("%v:%v", mntWSSrc, mntWSDest), // Set the mount for the working directory.
)
}

// Mount the shared directory if set.
if m.runConfig.AutoMountShared && mountWSShared {
containerExec.ArgsPre = append(containerExec.ArgsPre,
"-v",
strs.Fmt("%v:%v:ro", mntWSSharedSrc, mntWSSharedDest)) // Set the mount for the shared directory.
}

// Add all additional arguments.
containerExec.ArgsPre = append(containerExec.ArgsPre, m.runConfig.Args...)

if m.mgrType == ContainerManagerTypeV.Docker {
if runtime.GOOS != cm.WindowsOsName &&
runtime.GOOS != "darwin" {
Expand Down Expand Up @@ -241,6 +237,12 @@ func newManagerDocker(cmd string, mgrType ContainerManagerType) (mgr *ManagerDoc
cmdCtx := cm.NewCommandCtxBuilder().SetBaseCmd(cmd).EnableCaptureError().Build()
mgr = &ManagerDocker{cmdCtx: cmdCtx, uid: uid, gid: gid, mgrType: mgrType}

// Load the run config or default it.
mgr.runConfig, err = loadContainerRunConfig()
if err != nil {
err = cm.CombineErrors(err, cm.Error("Run config for containerized runs could not be loaded."))
}

return
}

Expand Down
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ testrules:
cd "{{root_dir}}" && \
tests/test-rules.sh

release-test:
release-test *args:
cd "{{root_dir}}/githooks" && \
GORELEASER_CURRENT_TAG=v9.9.9 \
goreleaser release --snapshot --clean --skip-sign --skip-publish --skip-validate
goreleaser release --snapshot --clean --skip=sign --skip=publish --skip=validate "$@"

release version update-info="":
cd "{{root_dir}}" && \
Expand Down
Loading

0 comments on commit 688ac3c

Please sign in to comment.