forked from wrenn/wrenn
Add USER, COPY, ENV persistence to template build system
Implement three new recipe commands for the admin template builder: - USER <name>: creates the user (adduser + passwordless sudo), switches execution context so subsequent RUN/START commands run as that user via su wrapping. Last USER becomes the template's default_user. - COPY <src> <dst>: copies files from an uploaded build archive (tar/tar.gz/zip) into the sandbox. Source paths validated against traversal. Ownership set to the current USER. - ENV persistence: accumulated env vars stored in templates.default_env (JSONB) and injected via PostInit when sandboxes are created from the template, mirroring Docker's image metadata approach. Supporting changes: - Pre-build creates wrenn-user as default (via USER command) - WORKDIR now creates the directory if it doesn't exist (mkdir -p) - Per-step progress updates (ProgressFunc callback) for live UI - Multipart form support on POST /v1/admin/builds for archive upload - Proto: default_user/default_env fields on Create/ResumeSandboxRequest - Host agent: SetDefaults calls PostInitWithDefaults on envd - Control plane: reads template defaults, passes on sandbox create/resume - Frontend: file upload widget, recipe copy button, keyword colors for USER/COPY, fixed Svelte whitespace stripping in step display - Admin panel defaults to /admin/templates instead of /admin/hosts - Migration adds default_user and default_env to templates and template_builds tables
This commit is contained in:
@ -24,9 +24,11 @@ type Step struct {
|
||||
Raw string // original string, preserved for logging
|
||||
Shell string // KindRUN, KindSTART: the shell command text
|
||||
Timeout time.Duration // KindRUN: 0 means use caller's default
|
||||
Key string // KindENV: variable name
|
||||
Key string // KindENV: variable name; KindUSER: username
|
||||
Value string // KindENV: variable value
|
||||
Path string // KindWORKDIR: directory path
|
||||
Src string // KindCOPY: source path (relative to build archive)
|
||||
Dst string // KindCOPY: destination path inside sandbox
|
||||
}
|
||||
|
||||
// ParseStep parses a single recipe instruction string into a Step.
|
||||
@ -61,9 +63,9 @@ func ParseStep(s string) (Step, error) {
|
||||
case "WORKDIR":
|
||||
return parseWORKDIR(s, rest)
|
||||
case "USER":
|
||||
return Step{Kind: KindUSER, Raw: s}, nil
|
||||
return parseUSER(s, rest)
|
||||
case "COPY":
|
||||
return Step{Kind: KindCOPY, Raw: s}, nil
|
||||
return parseCOPY(s, rest)
|
||||
default:
|
||||
return Step{}, fmt.Errorf("unknown instruction %q (expected RUN, START, ENV, WORKDIR, USER, or COPY)", keyword)
|
||||
}
|
||||
@ -127,3 +129,31 @@ func parseWORKDIR(raw, path string) (Step, error) {
|
||||
}
|
||||
return Step{Kind: KindWORKDIR, Raw: raw, Path: path}, nil
|
||||
}
|
||||
|
||||
func parseUSER(raw, username string) (Step, error) {
|
||||
if username == "" {
|
||||
return Step{}, fmt.Errorf("USER requires a username: %q", raw)
|
||||
}
|
||||
// Validate: alphanumeric, hyphens, underscores only; must start with a letter or underscore.
|
||||
for i, c := range username {
|
||||
if i == 0 && !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') {
|
||||
return Step{}, fmt.Errorf("USER username must start with a letter or underscore: %q", raw)
|
||||
}
|
||||
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-') {
|
||||
return Step{}, fmt.Errorf("USER username contains invalid character %q: %q", string(c), raw)
|
||||
}
|
||||
}
|
||||
return Step{Kind: KindUSER, Raw: raw, Key: username}, nil
|
||||
}
|
||||
|
||||
func parseCOPY(raw, rest string) (Step, error) {
|
||||
if rest == "" {
|
||||
return Step{}, fmt.Errorf("COPY requires <src> <dst>: %q", raw)
|
||||
}
|
||||
src, dst, found := strings.Cut(rest, " ")
|
||||
dst = strings.TrimSpace(dst)
|
||||
if !found || dst == "" {
|
||||
return Step{}, fmt.Errorf("COPY requires <src> <dst>: %q", raw)
|
||||
}
|
||||
return Step{Kind: KindCOPY, Raw: raw, Src: src, Dst: dst}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user