chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const gitTimeoutSeconds = 120
|
||||
|
||||
// gitRun ejecuta git con argumentos en cwd. Devuelve stdout, stderr, exitCode.
|
||||
func gitRun(cwd string, args ...string) (string, string, int, error) {
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
return "", "", -1, fmt.Errorf("git binary not in PATH")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), gitTimeoutSeconds*time.Second)
|
||||
defer cancel()
|
||||
c := exec.CommandContext(ctx, "git", args...) // #nosec G204 — args controlled
|
||||
if cwd != "" {
|
||||
c.Dir = cwd
|
||||
}
|
||||
var stdout, stderr strings.Builder
|
||||
c.Stdout = &stdout
|
||||
c.Stderr = &stderr
|
||||
err := c.Run()
|
||||
exitCode := 0
|
||||
if err != nil {
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
exitCode = ee.ExitCode()
|
||||
} else {
|
||||
return stdout.String(), stderr.String(), -1, err
|
||||
}
|
||||
}
|
||||
return stdout.String(), stderr.String(), exitCode, nil
|
||||
}
|
||||
|
||||
// runGitClone clona un repo a dest. Valida dest contra cap.PathsAllowed.
|
||||
func runGitClone(cap *Capability, args map[string]any) (any, int, error) {
|
||||
url := mapStringField(args, "url")
|
||||
dest := mapStringField(args, "dest")
|
||||
if url == "" || dest == "" {
|
||||
return nil, -1, fmt.Errorf("url and dest required")
|
||||
}
|
||||
if !isPathAllowed(dest, cap.PathsAllowed) {
|
||||
return nil, -1, fmt.Errorf("dest not allowed by manifest: %s", dest)
|
||||
}
|
||||
stdout, stderr, code, err := gitRun("", "clone", url, dest)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("git clone: %w (%s)", err, stderr)
|
||||
}
|
||||
if code != 0 {
|
||||
return map[string]any{
|
||||
"stdout": stdout,
|
||||
"stderr": stderr,
|
||||
"exit_code": code,
|
||||
}, code, fmt.Errorf("git clone exit=%d: %s", code, stderr)
|
||||
}
|
||||
// recoge HEAD commit + branch
|
||||
sha, _, _, _ := gitRun(dest, "rev-parse", "HEAD")
|
||||
branch, _, _, _ := gitRun(dest, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
return map[string]any{
|
||||
"dest": dest,
|
||||
"commit_sha": strings.TrimSpace(sha),
|
||||
"branch": strings.TrimSpace(branch),
|
||||
"stdout": stdout,
|
||||
"exit_code": 0,
|
||||
}, 0, nil
|
||||
}
|
||||
|
||||
// runGitCommit hace git add + commit en repo. files opcional; vacio = -am.
|
||||
func runGitCommit(cap *Capability, args map[string]any) (any, int, error) {
|
||||
repo := mapStringField(args, "repo")
|
||||
msg := mapStringField(args, "message")
|
||||
if repo == "" || msg == "" {
|
||||
return nil, -1, fmt.Errorf("repo and message required")
|
||||
}
|
||||
if !isPathAllowed(repo, cap.PathsAllowed) {
|
||||
return nil, -1, fmt.Errorf("repo not allowed by manifest: %s", repo)
|
||||
}
|
||||
var files []string
|
||||
if raw, ok := args["files"]; ok && raw != nil {
|
||||
if arr, ok := raw.([]any); ok {
|
||||
for _, v := range arr {
|
||||
if s, ok := v.(string); ok {
|
||||
files = append(files, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(files) > 0 {
|
||||
addArgs := append([]string{"add", "--"}, files...)
|
||||
_, addStderr, code, err := gitRun(repo, addArgs...)
|
||||
if err != nil || code != 0 {
|
||||
return nil, code, fmt.Errorf("git add: %s", addStderr)
|
||||
}
|
||||
_, cStderr, code, err := gitRun(repo, "commit", "-m", msg)
|
||||
if err != nil || code != 0 {
|
||||
return map[string]any{"stderr": cStderr, "exit_code": code}, code, fmt.Errorf("git commit exit=%d: %s", code, cStderr)
|
||||
}
|
||||
} else {
|
||||
_, cStderr, code, err := gitRun(repo, "commit", "-am", msg)
|
||||
if err != nil || code != 0 {
|
||||
return map[string]any{"stderr": cStderr, "exit_code": code}, code, fmt.Errorf("git commit exit=%d: %s", code, cStderr)
|
||||
}
|
||||
}
|
||||
sha, _, _, _ := gitRun(repo, "rev-parse", "HEAD")
|
||||
return map[string]any{
|
||||
"repo": repo,
|
||||
"commit_sha": strings.TrimSpace(sha),
|
||||
"exit_code": 0,
|
||||
}, 0, nil
|
||||
}
|
||||
|
||||
// runGitPush push del repo. remote default "origin", branch default "HEAD".
|
||||
func runGitPush(cap *Capability, args map[string]any) (any, int, error) {
|
||||
repo := mapStringField(args, "repo")
|
||||
if repo == "" {
|
||||
return nil, -1, fmt.Errorf("repo required")
|
||||
}
|
||||
if !isPathAllowed(repo, cap.PathsAllowed) {
|
||||
return nil, -1, fmt.Errorf("repo not allowed by manifest: %s", repo)
|
||||
}
|
||||
remote := mapStringField(args, "remote")
|
||||
if remote == "" {
|
||||
remote = "origin"
|
||||
}
|
||||
branch := mapStringField(args, "branch")
|
||||
if branch == "" {
|
||||
branch = "HEAD"
|
||||
}
|
||||
stdout, stderr, code, err := gitRun(repo, "push", remote, branch)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("git push: %w (%s)", err, stderr)
|
||||
}
|
||||
if code != 0 {
|
||||
return map[string]any{
|
||||
"stdout": stdout,
|
||||
"stderr": stderr,
|
||||
"exit_code": code,
|
||||
}, code, fmt.Errorf("git push exit=%d: %s", code, stderr)
|
||||
}
|
||||
return map[string]any{
|
||||
"ok": true,
|
||||
"remote": remote,
|
||||
"branch": branch,
|
||||
"stdout": stdout,
|
||||
"stderr": stderr,
|
||||
"exit_code": 0,
|
||||
}, 0, nil
|
||||
}
|
||||
Reference in New Issue
Block a user