Add build log file storage and download functionality

- Write deployment logs to files when deployment finishes (success or failure)
- Log files stored in DataDir/logs/<hostname>/<appname>/<appname>_<sha>_<timestamp>.log.txt
- Capture commit SHA for manual deploys by parsing git rev-parse HEAD after clone
- Add download endpoint for log files at /apps/{id}/deployments/{deploymentID}/download
- Add download link in deployment history view for finished deployments
This commit is contained in:
2026-01-01 06:08:00 -08:00
parent c4362c3143
commit 2cbcd3d72a
5 changed files with 226 additions and 23 deletions

View File

@@ -11,6 +11,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
@@ -417,7 +418,8 @@ type cloneConfig struct {
// CloneResult contains the result of a git clone operation.
type CloneResult struct {
Output string // Combined stdout/stderr from git clone
Output string // Combined stdout/stderr from git clone
CommitSHA string // The HEAD commit SHA after clone/checkout
}
// CloneRepo clones a git repository using SSH and optionally checks out a specific commit.
@@ -590,16 +592,22 @@ func (c *Client) createGitContainer(
if cfg.commitSHA != "" {
// Clone without depth limit so we can checkout any commit, then checkout specific SHA
// Using sh -c to run multiple commands - need to clear entrypoint
// Output "COMMIT:<sha>" marker at end for parsing
script := fmt.Sprintf(
"git clone --branch %s %s /repo && cd /repo && git checkout %s",
"git clone --branch %s %s /repo && cd /repo && git checkout %s && echo COMMIT:$(git rev-parse HEAD)",
cfg.branch, cfg.repoURL, cfg.commitSHA,
)
entrypoint = []string{}
cmd = []string{"sh", "-c", script}
} else {
// Shallow clone of branch HEAD - use default git entrypoint
entrypoint = nil
cmd = []string{"clone", "--depth", "1", "--branch", cfg.branch, cfg.repoURL, "/repo"}
// Shallow clone of branch HEAD, then output commit SHA
// Using sh -c to run multiple commands
script := fmt.Sprintf(
"git clone --depth 1 --branch %s %s /repo && cd /repo && echo COMMIT:$(git rev-parse HEAD)",
cfg.branch, cfg.repoURL,
)
entrypoint = []string{}
cmd = []string{"sh", "-c", script}
}
// Use host paths for Docker bind mounts (Docker runs on the host, not in our container)
@@ -657,10 +665,31 @@ func (c *Client) runGitClone(ctx context.Context, containerID string) (*CloneRes
)
}
return &CloneResult{Output: logs}, nil
// Parse commit SHA from output (looks for "COMMIT:<sha>" line)
commitSHA := parseCommitSHA(logs)
return &CloneResult{Output: logs, CommitSHA: commitSHA}, nil
}
}
// commitMarker is the prefix used to identify commit SHA in clone output.
const commitMarker = "COMMIT:"
// parseCommitSHA extracts the commit SHA from git clone output.
// It looks for a line starting with "COMMIT:" and returns the SHA after it.
func parseCommitSHA(output string) string {
for line := range strings.SplitSeq(output, "\n") {
line = strings.TrimSpace(line)
sha, found := strings.CutPrefix(line, commitMarker)
if found {
return strings.TrimSpace(sha)
}
}
return ""
}
func (c *Client) connect(ctx context.Context) error {
opts := []client.Opt{
client.FromEnv,