Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ COPY . /go
WORKDIR /go
RUN CGO_ENABLED=0 go build -ldflags "-X github.com/highcard-dev/daemon/internal.Version=${VERSION}" -o /out/druid-dev ./apps/druid-dev

FROM node:22-bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates git python3 make g++ vim && rm -rf /var/lib/apt/lists/*
FROM node:24-bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl git python3 make g++ vim && rm -rf /var/lib/apt/lists/*
ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo
ENV PATH=/usr/local/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable \
&& rustup target add wasm32-unknown-unknown
COPY --from=builder /out/druid-dev /usr/local/bin/druid-dev
ENTRYPOINT ["druid-dev"]
2 changes: 1 addition & 1 deletion apps/druid/core/services/runtime_supervisor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1678,7 +1678,7 @@ func (f *fakeWorkerBackend) RunCommand(command ports.RuntimeCommand) (*int, erro
}

func (f *fakeWorkerBackend) CreateUIPackageUpload(ctx context.Context, action ports.RuntimeUIPackageUploadAction) (ports.RuntimeUIPackageUpload, error) {
return ports.RuntimeUIPackageUpload{UploadURL: "http://uploads/" + action.RuntimeID + "/" + string(action.Scope), URL: "http://packages/" + action.RuntimeID + "/" + string(action.Scope) + "/app.wasm"}, nil
return ports.RuntimeUIPackageUpload{UploadURL: "http://uploads/" + action.RuntimeID + "/" + string(action.Scope), DocumentUploadURL: "http://uploads/" + action.RuntimeID + "/" + string(action.Scope) + "/app.openui", URL: "http://packages/" + action.RuntimeID + "/" + string(action.Scope) + "/app.wasm"}, nil
}

func (f *fakeWorkerBackend) ExpectedPorts(root string, commands map[string]*domain.CommandInstructionSet, ports []domain.Port) ([]domain.RuntimePortStatus, error) {
Expand Down
22 changes: 17 additions & 5 deletions apps/druid/core/services/runtime_ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (s *RuntimeSupervisor) PublishUIPackage(id string, scope string, sourcePath
if err != nil {
return nil, fmt.Errorf("create UI package upload: %w", err)
}
if upload.UploadURL == "" || upload.URL == "" {
if upload.UploadURL == "" || upload.DocumentUploadURL == "" || upload.URL == "" {
return nil, fmt.Errorf("UI package upload is incomplete")
}

Expand All @@ -56,8 +56,10 @@ func (s *RuntimeSupervisor) PublishUIPackage(id string, scope string, sourcePath
procedureName := domain.ProcedureName(commandName, 0, command.Procedures[0])
if err := session.AddTempItemWithWaitEnv(commandName, map[string]map[string]string{
procedureName: {
"DRUID_UI_SOURCE": sourcePath,
"DRUID_UI_UPLOAD_URL": upload.UploadURL,
"DRUID_UI_SOURCE": sourcePath,
"DRUID_UI_DOCUMENT_SOURCE": strings.TrimSuffix(sourcePath, ".wasm") + ".openui",
"DRUID_UI_UPLOAD_URL": upload.UploadURL,
"DRUID_UI_DOCUMENT_UPLOAD_URL": upload.DocumentUploadURL,
},
}); err != nil {
return nil, err
Expand Down Expand Up @@ -86,10 +88,20 @@ func uiPublishCommand() *domain.CommandInstructionSet {
WorkingDir: "/app/resources/deployment",
Mounts: []domain.Mount{{Path: "/app/resources/deployment", SubPath: ".", ReadOnly: true}},
Command: []string{"sh", "-ec", `
test -n "$(printenv DRUID_UI_SOURCE)" && test -n "$(printenv DRUID_UI_UPLOAD_URL)"
test -n "$(printenv DRUID_UI_SOURCE)"
test -n "$(printenv DRUID_UI_DOCUMENT_SOURCE)"
test -n "$(printenv DRUID_UI_UPLOAD_URL)"
test -n "$(printenv DRUID_UI_DOCUMENT_UPLOAD_URL)"
case "$(printenv DRUID_UI_SOURCE)" in private/*.wasm|public/*.wasm) ;; *) echo "invalid UI package path" >&2; exit 1;; esac
test -f "$(printenv DRUID_UI_SOURCE)" && test -s "$(printenv DRUID_UI_SOURCE)"
test -f "$(printenv DRUID_UI_SOURCE)"
test -s "$(printenv DRUID_UI_SOURCE)"
test -f "$(printenv DRUID_UI_DOCUMENT_SOURCE)"
test -s "$(printenv DRUID_UI_DOCUMENT_SOURCE)"
echo "Uploading Druid UI package"
curl --fail-with-body --silent --show-error --request PUT --upload-file "$(printenv DRUID_UI_DOCUMENT_SOURCE)" \
--header "Content-Type: text/plain; charset=utf-8" \
--header "Cache-Control: public, max-age=31536000, immutable" \
"$(printenv DRUID_UI_DOCUMENT_UPLOAD_URL)"
curl --fail-with-body --silent --show-error --request PUT --upload-file "$(printenv DRUID_UI_SOURCE)" \
--header "Content-Type: application/wasm" \
--header "Cache-Control: public, max-age=31536000, immutable" \
Expand Down
87 changes: 86 additions & 1 deletion apps/druid/core/services/runtime_ui_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package services

import (
"errors"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

Expand Down Expand Up @@ -73,7 +77,7 @@ func TestPublishUIPackageRunsOneShotCommandWithEphemeralURL(t *testing.T) {
}
procedure := domain.ProcedureName(command.Name, 0, command.Command.Procedures[0])
values := command.ProcedureEnv[procedure]
if values["DRUID_UI_SOURCE"] != "private/dist/app.wasm" || values["DRUID_UI_UPLOAD_URL"] == "" {
if values["DRUID_UI_SOURCE"] != "private/dist/app.wasm" || values["DRUID_UI_UPLOAD_URL"] == "" || values["DRUID_UI_DOCUMENT_UPLOAD_URL"] == "" {
t.Fatalf("procedure env = %#v", values)
}
script := command.Command.Procedures[0].Command[len(command.Command.Procedures[0].Command)-1]
Expand All @@ -84,3 +88,84 @@ func TestPublishUIPackageRunsOneShotCommandWithEphemeralURL(t *testing.T) {
t.Fatal("publish command must upload directly without digest negotiation")
}
}

func TestPublishUIPackageFailureKeepsPreviousPackage(t *testing.T) {
store := newTestStateStore(t)
previous := domain.RuntimeUIPackage{URL: "http://packages/previous/app.wasm", Path: "private/dist/app.wasm"}
if err := store.CreateScroll(&domain.RuntimeScroll{
ID: "ui-scroll", Root: "runtime://ui-scroll", ScrollName: "cached", ScrollYAML: cachedScrollYAML(""),
UIPackages: domain.RuntimeUIPackages{domain.RuntimeUIPackageScopePrivate: previous},
}); err != nil {
t.Fatal(err)
}
backend := &fakeWorkerBackend{runCommand: func(ports.RuntimeCommand) (*int, error) {
return nil, errors.New("document upload failed")
}}
supervisor := newRuntimeSupervisorForTest(t, store, coreservices.NewRuntimeScrollManager(store), backend)
if _, err := supervisor.PublishUIPackage("ui-scroll", "private", ""); err == nil {
t.Fatal("expected upload failure")
}
current, err := store.GetScroll("ui-scroll")
if err != nil {
t.Fatal(err)
}
if current.UIPackages[domain.RuntimeUIPackageScopePrivate] != previous {
t.Fatalf("failed publication changed active package: %#v", current.UIPackages)
}
}

func TestUIPublishRequiresBothAssetsAndStopsOnUploadFailure(t *testing.T) {
for _, test := range []struct {
name string
document bool
fail bool
wantErr bool
wantUploads int
}{
{"missing document", false, false, true, 0},
{"document upload fails", true, true, true, 1},
{"complete package", true, false, false, 2},
} {
t.Run(test.name, func(t *testing.T) {
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, "private"), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "private", "app.wasm"), []byte("wasm"), 0644); err != nil {
t.Fatal(err)
}
if test.document {
if err := os.WriteFile(filepath.Join(dir, "private", "app.openui"), []byte("root = Text(\"Test\")"), 0644); err != nil {
t.Fatal(err)
}
}
script := "#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"$UPLOAD_LOG\"\n"
if test.fail {
script += "exit 7\n"
}
if err := os.WriteFile(filepath.Join(dir, "curl"), []byte(script), 0755); err != nil {
t.Fatal(err)
}
args := uiPublishCommand().Procedures[0].Command[1:]
// Stored Scrolls expand environment variables before a worker sees the
// command. Request-scoped values must survive that round trip.
args[1] = os.ExpandEnv(args[1])
command := exec.Command("sh", args...)
command.Dir = dir
log := filepath.Join(dir, "uploads")
command.Env = append(os.Environ(), "PATH="+dir+":"+os.Getenv("PATH"), "UPLOAD_LOG="+log,
"DRUID_UI_SOURCE=private/app.wasm", "DRUID_UI_DOCUMENT_SOURCE=private/app.openui", "DRUID_UI_UPLOAD_URL=http://uploads/app.wasm", "DRUID_UI_DOCUMENT_UPLOAD_URL=http://uploads/app.openui")
output, err := command.CombinedOutput()
if (err != nil) != test.wantErr {
t.Fatalf("error = %v, output = %s", err, output)
}
data, _ := os.ReadFile(log)
if got := strings.Count(string(data), "--upload-file"); got != test.wantUploads {
t.Fatalf("uploads = %d, want %d", got, test.wantUploads)
}
if test.wantUploads == 2 && !strings.Contains(string(data), "app.openui") {
t.Fatal("missing document upload")
}
})
}
}
5 changes: 3 additions & 2 deletions internal/core/ports/services_ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ type RuntimeUIPackageUploadAction struct {
// RuntimeUIPackageUpload exposes a short-lived upload URL and the immutable
// public URL of the unique object it writes.
type RuntimeUIPackageUpload struct {
UploadURL string
URL string
UploadURL string
DocumentUploadURL string
URL string
}

type RuntimeMaterialization struct {
Expand Down
6 changes: 5 additions & 1 deletion internal/runtime/docker/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ func (b *Backend) CreateUIPackageUpload(ctx context.Context, action ports.Runtim
if err != nil {
return ports.RuntimeUIPackageUpload{}, err
}
return ports.RuntimeUIPackageUpload{UploadURL: uploadURL, URL: b.uiPackagePublicURL(config, action)}, nil
documentURL, err := uipackage.PresignPut(ctx, path.Join(action.RequestID, "app.openui"), config)
if err != nil {
return ports.RuntimeUIPackageUpload{}, err
}
return ports.RuntimeUIPackageUpload{UploadURL: uploadURL, DocumentUploadURL: documentURL, URL: b.uiPackagePublicURL(config, action)}, nil
}

func (b *Backend) uiPackagePublicURL(config uipackage.S3Config, action ports.RuntimeUIPackageUploadAction) string {
Expand Down
6 changes: 5 additions & 1 deletion internal/runtime/kubernetes/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ func (b *Backend) CreateUIPackageUpload(ctx context.Context, action ports.Runtim
if err != nil {
return ports.RuntimeUIPackageUpload{}, err
}
return ports.RuntimeUIPackageUpload{UploadURL: uploadURL, URL: b.uiPackagePublicURL(config, action)}, nil
documentURL, err := uipackage.PresignPut(ctx, path.Join(action.RequestID, "app.openui"), config)
if err != nil {
return ports.RuntimeUIPackageUpload{}, err
}
return ports.RuntimeUIPackageUpload{UploadURL: uploadURL, DocumentUploadURL: documentURL, URL: b.uiPackagePublicURL(config, action)}, nil
}

func (b *Backend) uiPackagePublicURL(config uipackage.S3Config, action ports.RuntimeUIPackageUploadAction) string {
Expand Down
Loading