Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Let a job declare the secrets it reads

4ac42680eca3181efc70099f65d6e2c9f45e9dd2 · Keenan Nemetz · 7d ago · parent 0c40a1d3

Let a job declare the secrets it reads

A job now names the repository secrets it needs, and the instance delivers
those values when the job is leased. The workflow carries names only, so the
queue row, the metadata branch and the run record never hold a value: the
record names the version of each value the job ran with, which is what makes a
result say which one it used.

    jobs:
      deploy:
        image: alpine:3.22
        secrets: [DEPLOY_KEY]

A name the repository has no value for is not a failure. The variable is left
unset rather than empty, the step's shell can tell the difference, and the
runner writes one line to the log saying which value was missing. That keeps
an optional secret optional and leaves "required" with the step, where someone
can write the message: ${DEPLOY_KEY:?DEPLOY_KEY is not set}.

The value reaches the step through a file the runner writes into the run
directory it already mounts at /run/vole for an event payload, and each step is
prefixed with `. /run/vole/secrets.env`. That is deliberately not an env-file
argument to the runtime, which cannot carry a line break, and not --env, which
puts the value in the process's argument list where every process on the host
can read it. A local `vole workflow run` writes the same file, reading the
values with the developer's own credential, and says which ones it could not
read.

The name rule and the 128 KiB limit moved to ci, next to the environment rules
they mirror, so the store refuses a name no workflow could declare and the
workflow refuses a name no process environment could carry.

Slice 2 of #154.

server/integration_test.go +52−1

@@ -45,6 +45,12 @@ var binPath string
45 45 // and otherwise answers the runtime commands the agent uses.
46 46 var fakeRuntimeDir string
47 47
48 // fakeRuntimeState is where the fake runtime remembers the volumes a container was
49 // started with, so exec can resolve a container path (/workspace, /run/vole) to the
50 // host directory the agent actually wrote. Without it a step could not read the
51 // files the runner prepares, and the tests that exercise them would prove nothing.
52 var fakeRuntimeState string
53
48 54 const fakeDocker = `#!/bin/sh
49 55 # A Docker-shaped test runtime. It does not isolate anything; it runs the step in
50 56 # the job tree so integration tests can exercise the runner without a daemon.
@@ -61,6 +67,34 @@ case "$cmd" in
61 67 exit 0
62 68 ;;
63 69 run)
70 name=""
71 volumes=""
72 while [ $# -gt 0 ]; do
73 case "$1" in
74 --name)
75 name="$2"
76 shift 2
77 ;;
78 --volume)
79 volumes="$volumes$2"
80 volumes="$volumes
81 "
82 shift 2
83 ;;
84 --*)
85 shift
86 ;;
87 -*)
88 shift
89 ;;
90 *)
91 shift
92 ;;
93 esac
94 done
95 if [ -n "$name" ] && [ -n "$FAKE_RUNTIME_STATE" ]; then
96 printf '%s' "$volumes" > "$FAKE_RUNTIME_STATE/$name"
97 fi
64 98 exit 0
65 99 ;;
66 100 exec)
@@ -72,10 +106,21 @@ case "$cmd" in
72 106 *) break ;;
73 107 esac
74 108 done
109 container="$1"
75 110 [ $# -gt 0 ] && shift
76 111 [ $# -gt 0 ] && shift
77 112 [ $# -gt 0 ] && shift
78 113 script="$1"
114 if [ -n "$FAKE_RUNTIME_STATE" ] && [ -f "$FAKE_RUNTIME_STATE/$container" ]; then
115 while IFS= read -r pair; do
116 [ -n "$pair" ] || continue
117 host="${pair%%:*}"
118 rest="${pair#*:}"
119 where="${rest%%:*}"
120 [ -n "$where" ] || continue
121 script=$(printf '%s' "$script" | sed "s|$where|$host|g")
122 done < "$FAKE_RUNTIME_STATE/$container"
123 fi
79 124 CI=true VOLE_CI=1 HOME=/tmp GIT_TERMINAL_PROMPT=0 /bin/sh -c "$script"
80 125 exit $?
81 126 ;;
@@ -116,6 +161,11 @@ func TestMain(m *testing.M) {
116 161 os.Exit(1)
117 162 }
118 163 fakeRuntimeDir = filepath.Join(dir, "runtime")
164 fakeRuntimeState = filepath.Join(dir, "runtime-state")
165 if err := os.MkdirAll(fakeRuntimeState, 0o755); err != nil {
166 fmt.Fprintln(os.Stderr, err)
167 os.Exit(1)
168 }
119 169 if err := os.MkdirAll(fakeRuntimeDir, 0o755); err != nil {
120 170 fmt.Fprintln(os.Stderr, err)
121 171 os.Exit(1)
@@ -2759,7 +2809,8 @@ func (f *fixture) ciEnv() []string { return f.ciEnvFor(f.keyPath) }
2759 2809 func (f *fixture) ciEnvFor(keyPath string) []string {
2760 2810 sshCmd := "ssh -i " + keyPath + " -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
2761 2811 env := append(os.Environ(), "VOLE_SSH_COMMAND="+sshCmd, "GIT_SSH_COMMAND="+sshCmd,
2762 "GIT_TERMINAL_PROMPT=0", "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null")
2812 "GIT_TERMINAL_PROMPT=0", "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null",
2813 "FAKE_RUNTIME_STATE="+fakeRuntimeState)
2763 2814 // The runner requires an OCI runtime. Put the test script first so this package
2764 2815 // does not depend on Docker being installed or usable in a sandboxed test host.
2765 2816 for i, kv := range env {