fix: stream platform builder progress
This commit is contained in:
@@ -88,7 +88,16 @@ func (svc *CoreService) executeDistributionBuild(job domain.Job) error {
|
|||||||
if isTerminalJobState(job.State) {
|
if isTerminalJobState(job.State) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
payload, buildErr := svc.configuredDistributionBuilder().Build(input)
|
builder := svc.configuredDistributionBuilder()
|
||||||
|
var payload []byte
|
||||||
|
var buildErr error
|
||||||
|
if progressBuilder, ok := builder.(distributionBuilderWithProgress); ok {
|
||||||
|
payload, buildErr = progressBuilder.BuildWithProgress(input, func(progress DistributionBuildProgress) {
|
||||||
|
_ = svc.updateDistributionBuildProgress(job, progress)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
payload, buildErr = builder.Build(input)
|
||||||
|
}
|
||||||
if buildErr != nil {
|
if buildErr != nil {
|
||||||
return svc.failDistributionBuildJob(job, builderJobFailureMessage(buildErr))
|
return svc.failDistributionBuildJob(job, builderJobFailureMessage(buildErr))
|
||||||
}
|
}
|
||||||
@@ -432,7 +441,7 @@ func (svc *CoreService) markDistributionBuildRunning(job *domain.Job) error {
|
|||||||
}
|
}
|
||||||
current.State = domain.JobStateRunning
|
current.State = domain.JobStateRunning
|
||||||
current.Attempt = maxInt(current.Attempt, 1)
|
current.Attempt = maxInt(current.Attempt, 1)
|
||||||
current.Progress = domain.JobProgress{Percent: 5, Phase: current.Progress.Phase, Message: "platform builder started"}
|
current.Progress = domain.JobProgress{Percent: 5, Phase: current.Progress.Phase, Message: "env_check: platform builder started"}
|
||||||
current.UpdatedAt = stamp
|
current.UpdatedAt = stamp
|
||||||
if err := svc.updateScheduledJob(current); err != nil {
|
if err := svc.updateScheduledJob(current); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -444,6 +453,29 @@ func (svc *CoreService) markDistributionBuildRunning(job *domain.Job) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *CoreService) updateDistributionBuildProgress(job domain.Job, progress DistributionBuildProgress) error {
|
||||||
|
stamp := svc.now()
|
||||||
|
svc.jobMu.Lock()
|
||||||
|
defer svc.jobMu.Unlock()
|
||||||
|
|
||||||
|
current, err := svc.store.Jobs().Get(job.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isTerminalJobState(current.State) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if progress.Percent < current.Progress.Percent {
|
||||||
|
progress.Percent = current.Progress.Percent
|
||||||
|
}
|
||||||
|
current.Progress = domain.JobProgress{Percent: progress.Percent, Phase: current.Progress.Phase, Message: strings.TrimSpace(progress.Message)}
|
||||||
|
current.UpdatedAt = stamp
|
||||||
|
if err := svc.updateScheduledJob(current); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return svc.projectDistributionBuildProgress(current, stamp)
|
||||||
|
}
|
||||||
|
|
||||||
func (svc *CoreService) succeedDistributionBuildJob(job domain.Job, artifactID string) error {
|
func (svc *CoreService) succeedDistributionBuildJob(job domain.Job, artifactID string) error {
|
||||||
stamp := svc.now()
|
stamp := svc.now()
|
||||||
svc.jobMu.Lock()
|
svc.jobMu.Lock()
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ type captureDistributionBuilder struct {
|
|||||||
payload []byte
|
payload []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type progressDistributionBuilder struct {
|
||||||
|
started chan struct{}
|
||||||
|
release <-chan struct{}
|
||||||
|
payload []byte
|
||||||
|
}
|
||||||
|
|
||||||
func (builder captureDistributionBuilder) Readiness() (bool, string) {
|
func (builder captureDistributionBuilder) Readiness() (bool, string) {
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
@@ -31,6 +37,27 @@ func (builder captureDistributionBuilder) Build(input domain.DistributionBuildIn
|
|||||||
return domain.CopyBytes(builder.payload), nil
|
return domain.CopyBytes(builder.payload), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (builder progressDistributionBuilder) Readiness() (bool, string) {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (builder progressDistributionBuilder) Build(input domain.DistributionBuildInput) ([]byte, error) {
|
||||||
|
return builder.BuildWithProgress(input, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (builder progressDistributionBuilder) BuildWithProgress(_ domain.DistributionBuildInput, progress func(DistributionBuildProgress)) ([]byte, error) {
|
||||||
|
if progress != nil {
|
||||||
|
progress(DistributionBuildProgress{Percent: 72, Message: "build_compile: compiling run target executable"})
|
||||||
|
}
|
||||||
|
if builder.started != nil {
|
||||||
|
builder.started <- struct{}{}
|
||||||
|
}
|
||||||
|
if builder.release != nil {
|
||||||
|
<-builder.release
|
||||||
|
}
|
||||||
|
return domain.CopyBytes(builder.payload), nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestCoreServiceKeepsPlatformBuildKeyOffMachineJobChannel(t *testing.T) {
|
func TestCoreServiceKeepsPlatformBuildKeyOffMachineJobChannel(t *testing.T) {
|
||||||
svc, session, instance := newDistributionTestFixture(t)
|
svc, session, instance := newDistributionTestFixture(t)
|
||||||
plugin, err := svc.store.GamePlugins().Get(instance.PluginID)
|
plugin, err := svc.store.GamePlugins().Get(instance.PluginID)
|
||||||
@@ -319,6 +346,39 @@ func TestCoreServiceDoesNotDuplicateInFlightPlatformBuild(t *testing.T) {
|
|||||||
completeDistributionBuild(t, svc, first, nil)
|
completeDistributionBuild(t, svc, first, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCoreServiceProjectsPlatformBuilderProgressBeforeCompletion(t *testing.T) {
|
||||||
|
svc, session, instance := newDistributionTestFixture(t)
|
||||||
|
started := make(chan struct{}, 1)
|
||||||
|
release := make(chan struct{})
|
||||||
|
var releaseOnce sync.Once
|
||||||
|
t.Cleanup(func() { releaseOnce.Do(func() { close(release) }) })
|
||||||
|
svc.ConfigureDistributionBuilder(progressDistributionBuilder{started: started, release: release, payload: []byte("progress-platform-build")})
|
||||||
|
|
||||||
|
distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{
|
||||||
|
ServerInstanceID: instance.ID,
|
||||||
|
TargetOS: "linux",
|
||||||
|
TargetArch: "amd64",
|
||||||
|
IdempotencyKey: "platform-builder-progress",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generate platform distribution: %v", err)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-started:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("platform builder did not report progress")
|
||||||
|
}
|
||||||
|
job, err := svc.GetJob(distribution.BuildJobID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get progress job: %v", err)
|
||||||
|
}
|
||||||
|
if job.State != domain.JobStateRunning || job.Progress.Percent != 72 || job.Progress.Message != "build_compile: compiling run target executable" {
|
||||||
|
t.Fatalf("expected in-flight platform builder progress, got %+v", job)
|
||||||
|
}
|
||||||
|
releaseOnce.Do(func() { close(release) })
|
||||||
|
completeDistributionBuild(t, svc, distribution, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCoreServiceDiscardsBuildCompletedAfterKeyReset(t *testing.T) {
|
func TestCoreServiceDiscardsBuildCompletedAfterKeyReset(t *testing.T) {
|
||||||
svc, session, instance := newDistributionTestFixture(t)
|
svc, session, instance := newDistributionTestFixture(t)
|
||||||
inputs := make(chan domain.DistributionBuildInput, 1)
|
inputs := make(chan domain.DistributionBuildInput, 1)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
@@ -11,10 +12,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"browser.local/platform/domain"
|
"browser.local/platform/domain"
|
||||||
@@ -33,6 +37,15 @@ type DistributionBuilder interface {
|
|||||||
Build(input domain.DistributionBuildInput) ([]byte, error)
|
Build(input domain.DistributionBuildInput) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DistributionBuildProgress struct {
|
||||||
|
Percent int
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type distributionBuilderWithProgress interface {
|
||||||
|
BuildWithProgress(input domain.DistributionBuildInput, progress func(DistributionBuildProgress)) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
// DockerDistributionBuilderConfig configures a container-per-build builder.
|
// DockerDistributionBuilderConfig configures a container-per-build builder.
|
||||||
type DockerDistributionBuilderConfig struct {
|
type DockerDistributionBuilderConfig struct {
|
||||||
DockerBinary string
|
DockerBinary string
|
||||||
@@ -42,6 +55,7 @@ type DockerDistributionBuilderConfig struct {
|
|||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
PlatformURL string
|
PlatformURL string
|
||||||
CommandRunner func(ctx context.Context, name string, args ...string) ([]byte, error)
|
CommandRunner func(ctx context.Context, name string, args ...string) ([]byte, error)
|
||||||
|
CommandStream func(ctx context.Context, name string, args []string, onLine func(string)) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DockerDistributionBuilder runs each build in a container from a pinned image,
|
// DockerDistributionBuilder runs each build in a container from a pinned image,
|
||||||
@@ -52,6 +66,7 @@ type DockerDistributionBuilder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDockerDistributionBuilder(config DockerDistributionBuilderConfig) *DockerDistributionBuilder {
|
func NewDockerDistributionBuilder(config DockerDistributionBuilderConfig) *DockerDistributionBuilder {
|
||||||
|
customRunner := config.CommandRunner != nil
|
||||||
if strings.TrimSpace(config.DockerBinary) == "" {
|
if strings.TrimSpace(config.DockerBinary) == "" {
|
||||||
config.DockerBinary = "docker"
|
config.DockerBinary = "docker"
|
||||||
}
|
}
|
||||||
@@ -61,6 +76,9 @@ func NewDockerDistributionBuilder(config DockerDistributionBuilderConfig) *Docke
|
|||||||
if config.CommandRunner == nil {
|
if config.CommandRunner == nil {
|
||||||
config.CommandRunner = runCommandCombined
|
config.CommandRunner = runCommandCombined
|
||||||
}
|
}
|
||||||
|
if config.CommandStream == nil && !customRunner {
|
||||||
|
config.CommandStream = runCommandStreamCombined
|
||||||
|
}
|
||||||
return &DockerDistributionBuilder{config: config}
|
return &DockerDistributionBuilder{config: config}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +86,46 @@ func runCommandCombined(ctx context.Context, name string, args ...string) ([]byt
|
|||||||
return exec.CommandContext(ctx, name, args...).CombinedOutput()
|
return exec.CommandContext(ctx, name, args...).CombinedOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runCommandStreamCombined(ctx context.Context, name string, args []string, onLine func(string)) ([]byte, error) {
|
||||||
|
cmd := exec.CommandContext(ctx, name, args...)
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
var output bytes.Buffer
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
capture := func(reader io.Reader) {
|
||||||
|
defer wg.Done()
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
mu.Lock()
|
||||||
|
output.WriteString(line)
|
||||||
|
output.WriteByte('\n')
|
||||||
|
mu.Unlock()
|
||||||
|
if onLine != nil {
|
||||||
|
onLine(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Add(2)
|
||||||
|
go capture(stdout)
|
||||||
|
go capture(stderr)
|
||||||
|
err = cmd.Wait()
|
||||||
|
wg.Wait()
|
||||||
|
return output.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
func (builder *DockerDistributionBuilder) Readiness() (bool, string) {
|
func (builder *DockerDistributionBuilder) Readiness() (bool, string) {
|
||||||
if strings.TrimSpace(builder.config.Image) == "" {
|
if strings.TrimSpace(builder.config.Image) == "" {
|
||||||
return false, "platform builder image is not configured"
|
return false, "platform builder image is not configured"
|
||||||
@@ -124,9 +182,14 @@ func pinnedBuilderImage(image string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (builder *DockerDistributionBuilder) Build(input domain.DistributionBuildInput) ([]byte, error) {
|
func (builder *DockerDistributionBuilder) Build(input domain.DistributionBuildInput) ([]byte, error) {
|
||||||
|
return builder.BuildWithProgress(input, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (builder *DockerDistributionBuilder) BuildWithProgress(input domain.DistributionBuildInput, progress func(DistributionBuildProgress)) ([]byte, error) {
|
||||||
if ready, reason := builder.Readiness(); !ready {
|
if ready, reason := builder.Readiness(); !ready {
|
||||||
return nil, validationError(reason)
|
return nil, validationError(reason)
|
||||||
}
|
}
|
||||||
|
reportBuilderProgress(progress, 8, "env_check: platform builder readiness verified")
|
||||||
sourceDir, err := filepath.Abs(strings.TrimSpace(builder.config.SourceDir))
|
sourceDir, err := filepath.Abs(strings.TrimSpace(builder.config.SourceDir))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, validationError("platform builder run source directory is invalid")
|
return nil, validationError("platform builder run source directory is invalid")
|
||||||
@@ -150,6 +213,7 @@ func (builder *DockerDistributionBuilder) Build(input domain.DistributionBuildIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer func() { _ = os.RemoveAll(jobDir) }()
|
defer func() { _ = os.RemoveAll(jobDir) }()
|
||||||
|
reportBuilderProgress(progress, 14, "env_check: platform builder workspace prepared")
|
||||||
|
|
||||||
// The auth key reaches the container through a per-job input file, never
|
// The auth key reaches the container through a per-job input file, never
|
||||||
// through a job-channel response to a machine-side endpoint or a container
|
// through a job-channel response to a machine-side endpoint or a container
|
||||||
@@ -193,12 +257,15 @@ func (builder *DockerDistributionBuilder) Build(input domain.DistributionBuildIn
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), builder.config.Timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), builder.config.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
args := builder.containerArgs(input, sourceDir, inputDir, buildDir, outputDir, outputName)
|
args := builder.containerArgs(input, sourceDir, inputDir, buildDir, outputDir, outputName)
|
||||||
if output, err := builder.config.CommandRunner(ctx, builder.config.DockerBinary, args...); err != nil {
|
reportBuilderProgress(progress, 18, "git_sync: platform builder container starting")
|
||||||
|
output, err := builder.runBuildCommand(ctx, args, progress)
|
||||||
|
if err != nil {
|
||||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||||
return nil, validationError("platform builder timed out while building the distribution")
|
return nil, validationError("platform builder timed out while building the distribution")
|
||||||
}
|
}
|
||||||
return nil, validationError("platform builder failed: " + safeBuilderFailure(output, input.AuthKey, builder.config.SourceDir, sourceDir, jobDir))
|
return nil, validationError("platform builder failed: " + safeBuilderFailure(output, input.AuthKey, builder.config.SourceDir, sourceDir, jobDir))
|
||||||
}
|
}
|
||||||
|
reportBuilderProgress(progress, 92, "package_finalize: reading platform builder output")
|
||||||
binary, err := os.ReadFile(filepath.Join(outputDir, outputName))
|
binary, err := os.ReadFile(filepath.Join(outputDir, outputName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, validationError("platform builder did not produce a distribution executable")
|
return nil, validationError("platform builder did not produce a distribution executable")
|
||||||
@@ -216,14 +283,74 @@ func (builder *DockerDistributionBuilder) Build(input domain.DistributionBuildIn
|
|||||||
return packageClientManagerDistribution(input.PackageFormat, outputName, binary, configPayload)
|
return packageClientManagerDistribution(input.PackageFormat, outputName, binary, configPayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (builder *DockerDistributionBuilder) runBuildCommand(ctx context.Context, args []string, progress func(DistributionBuildProgress)) ([]byte, error) {
|
||||||
|
if builder.config.CommandStream == nil {
|
||||||
|
return builder.config.CommandRunner(ctx, builder.config.DockerBinary, args...)
|
||||||
|
}
|
||||||
|
return builder.config.CommandStream(ctx, builder.config.DockerBinary, args, func(line string) {
|
||||||
|
parsed, ok := parseBuilderProgressLine(line)
|
||||||
|
if ok {
|
||||||
|
reportBuilderProgress(progress, parsed.Percent, parsed.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const builderProgressMarker = "__platform_builder_progress__|"
|
||||||
|
|
||||||
|
func parseBuilderProgressLine(line string) (DistributionBuildProgress, bool) {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if !strings.HasPrefix(line, builderProgressMarker) {
|
||||||
|
return DistributionBuildProgress{}, false
|
||||||
|
}
|
||||||
|
rest := strings.TrimPrefix(line, builderProgressMarker)
|
||||||
|
parts := strings.SplitN(rest, "|", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return DistributionBuildProgress{}, false
|
||||||
|
}
|
||||||
|
percent, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||||
|
if err != nil {
|
||||||
|
return DistributionBuildProgress{}, false
|
||||||
|
}
|
||||||
|
message := strings.TrimSpace(parts[1])
|
||||||
|
if message == "" {
|
||||||
|
return DistributionBuildProgress{}, false
|
||||||
|
}
|
||||||
|
if percent < 1 {
|
||||||
|
percent = 1
|
||||||
|
}
|
||||||
|
if percent > 99 {
|
||||||
|
percent = 99
|
||||||
|
}
|
||||||
|
return DistributionBuildProgress{Percent: percent, Message: message}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func reportBuilderProgress(progress func(DistributionBuildProgress), percent int, message string) {
|
||||||
|
if progress == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if percent < 1 {
|
||||||
|
percent = 1
|
||||||
|
}
|
||||||
|
if percent > 99 {
|
||||||
|
percent = 99
|
||||||
|
}
|
||||||
|
progress(DistributionBuildProgress{Percent: percent, Message: message})
|
||||||
|
}
|
||||||
|
|
||||||
const distributionBuildScript = `#!/bin/sh
|
const distributionBuildScript = `#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
|
progress() {
|
||||||
|
printf '__platform_builder_progress__|%s|%s\n' "$1" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
auth_key="$(cat /workspace/input/auth-key)"
|
auth_key="$(cat /workspace/input/auth-key)"
|
||||||
if [ "$COMPONENT_KIND" = "run" ]; then
|
if [ "$COMPONENT_KIND" = "run" ]; then
|
||||||
|
progress 24 'git_sync: preparing run source snapshot'
|
||||||
rm -rf /workspace/build/run-source
|
rm -rf /workspace/build/run-source
|
||||||
mkdir -p /workspace/build/run-source
|
mkdir -p /workspace/build/run-source
|
||||||
cp -R /workspace/source/. /workspace/build/run-source/
|
cp -R /workspace/source/. /workspace/build/run-source/
|
||||||
|
progress 34 'env_check: injecting run build metadata'
|
||||||
seed_b64="$(base64 /workspace/input/workspace-seed.json | tr -d '\n')"
|
seed_b64="$(base64 /workspace/input/workspace-seed.json | tr -d '\n')"
|
||||||
cat > /workspace/build/run-source/config/workspace_seed_generated.go <<EOF
|
cat > /workspace/build/run-source/config/workspace_seed_generated.go <<EOF
|
||||||
package config
|
package config
|
||||||
@@ -243,8 +370,11 @@ EOF
|
|||||||
ldflags="$ldflags -X browser.local/run/config.BuildComponentKey=$PROFILE_KEY"
|
ldflags="$ldflags -X browser.local/run/config.BuildComponentKey=$PROFILE_KEY"
|
||||||
ldflags="$ldflags -X browser.local/run/config.BuildKeyGeneration=$KEY_GENERATION"
|
ldflags="$ldflags -X browser.local/run/config.BuildKeyGeneration=$KEY_GENERATION"
|
||||||
ldflags="$ldflags -X browser.local/run/config.BuildVersion=$TARGET_RELEASE"
|
ldflags="$ldflags -X browser.local/run/config.BuildVersion=$TARGET_RELEASE"
|
||||||
|
progress 48 'build_compile: downloading Go modules'
|
||||||
go mod download
|
go mod download
|
||||||
|
progress 72 'build_compile: compiling run target executable'
|
||||||
go build -trimpath -ldflags "$ldflags" -o "/workspace/output/$OUTPUT_FILENAME" ./cmd/run
|
go build -trimpath -ldflags "$ldflags" -o "/workspace/output/$OUTPUT_FILENAME" ./cmd/run
|
||||||
|
progress 88 'package_finalize: run executable written'
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -257,10 +387,12 @@ case "$REPOSITORY_URL" in
|
|||||||
*) printf 'client-manager repository must use https\n' >&2; exit 2 ;;
|
*) printf 'client-manager repository must use https\n' >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
cd /workspace/build
|
cd /workspace/build
|
||||||
|
progress 24 'git_sync: fetching client-manager source'
|
||||||
git init --quiet
|
git init --quiet
|
||||||
git remote add origin "$REPOSITORY_URL"
|
git remote add origin "$REPOSITORY_URL"
|
||||||
git fetch --quiet --depth 1 origin "$SOURCE_REVISION"
|
git fetch --quiet --depth 1 origin "$SOURCE_REVISION"
|
||||||
git checkout --quiet --detach FETCH_HEAD
|
git checkout --quiet --detach FETCH_HEAD
|
||||||
|
progress 36 'env_check: writing client-manager configuration'
|
||||||
{
|
{
|
||||||
printf 'server_url: "%s"\n' "$PLATFORM_URL"
|
printf 'server_url: "%s"\n' "$PLATFORM_URL"
|
||||||
printf 'server_instance_id: "%s"\n' "$SERVER_INSTANCE_ID"
|
printf 'server_instance_id: "%s"\n' "$SERVER_INSTANCE_ID"
|
||||||
@@ -270,9 +402,12 @@ git checkout --quiet --detach FETCH_HEAD
|
|||||||
printf 'scum_client_machine_label: "managed-client"\n'
|
printf 'scum_client_machine_label: "managed-client"\n'
|
||||||
printf 'ftp_provider: 3\n'
|
printf 'ftp_provider: 3\n'
|
||||||
} > config.yaml
|
} > config.yaml
|
||||||
|
progress 52 'deps_download: downloading Go modules'
|
||||||
go mod download
|
go mod download
|
||||||
|
progress 76 'build_compile: compiling client-manager executable'
|
||||||
go build -trimpath -ldflags '-s -w' -o "/workspace/output/$OUTPUT_FILENAME" .
|
go build -trimpath -ldflags '-s -w' -o "/workspace/output/$OUTPUT_FILENAME" .
|
||||||
cp config.yaml /workspace/output/config.yaml
|
cp config.yaml /workspace/output/config.yaml
|
||||||
|
progress 88 'package_finalize: client-manager package inputs written'
|
||||||
`
|
`
|
||||||
|
|
||||||
func (builder *DockerDistributionBuilder) containerArgs(input domain.DistributionBuildInput, sourceDir string, inputDir string, buildDir string, outputDir string, outputName string) []string {
|
func (builder *DockerDistributionBuilder) containerArgs(input domain.DistributionBuildInput, sourceDir string, inputDir string, buildDir string, outputDir string, outputName string) []string {
|
||||||
@@ -391,7 +526,7 @@ func safeBuilderFailure(output []byte, sensitiveValues ...string) string {
|
|||||||
kept := make([]string, 0, len(lines))
|
kept := make([]string, 0, len(lines))
|
||||||
for index := len(lines) - 1; index >= 0 && len(kept) < 3; index-- {
|
for index := len(lines) - 1; index >= 0 && len(kept) < 3; index-- {
|
||||||
line := strings.TrimSpace(lines[index])
|
line := strings.TrimSpace(lines[index])
|
||||||
if line == "" || strings.Contains(line, "/workspace/input") || strings.Contains(line, "auth-key") {
|
if line == "" || strings.HasPrefix(line, builderProgressMarker) || strings.Contains(line, "/workspace/input") || strings.Contains(line, "auth-key") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
line = redactBuilderHostPaths(line)
|
line = redactBuilderHostPaths(line)
|
||||||
|
|||||||
@@ -185,6 +185,58 @@ func TestDockerDistributionBuilderKeepsSecretInIsolatedInput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDockerDistributionBuilderStreamsProgressMarkers(t *testing.T) {
|
||||||
|
sourceDir := createBuilderSource(t)
|
||||||
|
workspaceDir := t.TempDir()
|
||||||
|
var progress []DistributionBuildProgress
|
||||||
|
builder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{
|
||||||
|
DockerBinary: "docker-test",
|
||||||
|
Image: "browser-platform-distribution-builder:1.0.0",
|
||||||
|
SourceDir: sourceDir,
|
||||||
|
WorkspaceDir: workspaceDir,
|
||||||
|
CommandRunner: func(_ context.Context, _ string, args ...string) ([]byte, error) {
|
||||||
|
if len(args) > 0 && (args[0] == "version" || args[0] == "image") {
|
||||||
|
return []byte("27.0.0"), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("unexpected non-stream command")
|
||||||
|
},
|
||||||
|
CommandStream: func(_ context.Context, _ string, args []string, onLine func(string)) ([]byte, error) {
|
||||||
|
onLine("__platform_builder_progress__|72|build_compile: compiling run target executable")
|
||||||
|
outputDir := builderMountHostPath(t, args, "/workspace/output")
|
||||||
|
if err := os.WriteFile(filepath.Join(outputDir, "run"), []byte("compiled-run"), 0o700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []byte("builder finished"), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
payload, err := builder.BuildWithProgress(domain.DistributionBuildInput{
|
||||||
|
JobID: "job-progress",
|
||||||
|
ComponentKind: domain.DistributionComponentRun,
|
||||||
|
PluginID: "game.scum",
|
||||||
|
TargetOS: "linux",
|
||||||
|
TargetArch: "amd64",
|
||||||
|
OutputFilename: "run",
|
||||||
|
AuthKey: "component-key",
|
||||||
|
}, func(item DistributionBuildProgress) {
|
||||||
|
progress = append(progress, item)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("build with progress: %v", err)
|
||||||
|
}
|
||||||
|
if string(payload) != "compiled-run" {
|
||||||
|
t.Fatalf("unexpected built payload %q", payload)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, item := range progress {
|
||||||
|
if item.Percent == 72 && item.Message == "build_compile: compiling run target executable" {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("streamed progress marker was not reported: %+v", progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDockerDistributionBuilderRedactsFailureAndTimeout(t *testing.T) {
|
func TestDockerDistributionBuilderRedactsFailureAndTimeout(t *testing.T) {
|
||||||
sourceDir := createBuilderSource(t)
|
sourceDir := createBuilderSource(t)
|
||||||
workspaceDir := t.TempDir()
|
workspaceDir := t.TempDir()
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export function useRuntimeTaskController() {
|
|||||||
status: "succeeded",
|
status: "succeeded",
|
||||||
percent: 100,
|
percent: 100,
|
||||||
stageStatus: Object.fromEntries(stages.map((item) => [item.key, "completed" as RuntimeTaskStageStatus])),
|
stageStatus: Object.fromEntries(stages.map((item) => [item.key, "completed" as RuntimeTaskStageStatus])),
|
||||||
logs: appendRuntimeLog(current.logs, "构建产物已由 run worker 上传")
|
logs: appendRuntimeLog(current.logs, "构建产物已由 platform builder 登记")
|
||||||
}
|
}
|
||||||
: current
|
: current
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user