Skip to content

Commit fd82a68

Browse files
committed
feat(webid): add HTTP API server with full resolver, renderer, and deploy script
Includes all fixes from initial integration: - wire WEBI_GIT_COMMIT_HASH through render context - match legacy API output format with production - wildcard OS/arch filter and libc sort order - arch waterfall + posix_2017 OS matching - prefer exact OS/arch matches over fallbacks in sort order - apply limit after sort, not during filter
1 parent 88bafaa commit fd82a68

10 files changed

Lines changed: 2270 additions & 2 deletions

File tree

_webi/package-install.tpl.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ $Env:WEBI_HOST = 'https://webinstall.dev'
1717
#$Env:PKG_NAME = node
1818
#$Env:WEBI_VERSION = v12.16.2
1919
#$Env:WEBI_GIT_TAG = 12.16.2
20+
#$Env:WEBI_GIT_COMMIT_HASH =
2021
#$Env:WEBI_PKG_URL = "https://.../node-....zip"
2122
#$Env:WEBI_PKG_FILE = "node-v12.16.2-win-x64.zip"
2223
#$Env:WEBI_PKG_PATHNAME = "node-v12.16.2-win-x64.zip"

_webi/package-install.tpl.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ __bootstrap_webi() {
1515
# TODO not sure if BUILD is the best name for this
1616
#WEBI_BUILD=
1717
#WEBI_GIT_TAG=
18+
#WEBI_GIT_COMMIT_HASH=
1819
#WEBI_LTS=
1920
#WEBI_CHANNEL=
2021
#WEBI_EXT=

cmd/webid/bootstrap_test.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package main
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/http/httptest"
7+
"strings"
8+
"testing"
9+
)
10+
11+
// TestBootstrapCurlPipe verifies the /{pkg} route returns the curl-pipe bootstrap.
12+
func TestBootstrapCurlPipe(t *testing.T) {
13+
srv, ts := newTestServer(t)
14+
15+
pkg := "bat"
16+
if srv.getPackage(pkg) == nil {
17+
t.Skipf("package %s not in cache", pkg)
18+
}
19+
20+
code, body := get(t, ts, "/bat@stable")
21+
if code != 200 {
22+
t.Fatalf("status %d: %s", code, body[:min(len(body), 200)])
23+
}
24+
25+
// Should contain the bootstrap env vars.
26+
if !strings.Contains(body, "WEBI_PKG=") {
27+
t.Error("missing WEBI_PKG= in bootstrap")
28+
}
29+
if !strings.Contains(body, "WEBI_HOST=") {
30+
t.Error("missing WEBI_HOST= in bootstrap")
31+
}
32+
if !strings.Contains(body, "WEBI_CHECKSUM=") {
33+
t.Error("missing WEBI_CHECKSUM= in bootstrap")
34+
}
35+
// Should NOT contain the full installer (install.sh content).
36+
// The bootstrap just downloads and runs webi.
37+
if strings.Contains(body, "pkg_install()") {
38+
t.Error("bootstrap should not contain pkg_install — that's the full installer")
39+
}
40+
41+
t.Logf("bootstrap size: %d bytes", len(body))
42+
}
43+
44+
// TestInstallerFull verifies /api/installers/{pkg}.sh returns the full installer.
45+
func TestInstallerFull(t *testing.T) {
46+
srv, ts := newTestServer(t)
47+
48+
pkg := "bat"
49+
if srv.getPackage(pkg) == nil {
50+
t.Skipf("package %s not in cache", pkg)
51+
}
52+
53+
// Use a webi-style User-Agent so the server can detect platform.
54+
code, body := getWithUA(t, ts, "/api/installers/bat@stable.sh", "aarch64/unknown Darwin/24.2.0 libc")
55+
if code != 200 {
56+
t.Fatalf("status %d: %s", code, body[:min(len(body), 500)])
57+
}
58+
59+
// Should contain resolved release info.
60+
if !strings.Contains(body, "WEBI_VERSION=") {
61+
t.Error("missing WEBI_VERSION= in installer")
62+
}
63+
if !strings.Contains(body, "WEBI_PKG_URL=") {
64+
t.Error("missing WEBI_PKG_URL= in installer")
65+
}
66+
if !strings.Contains(body, "PKG_NAME=") {
67+
t.Error("missing PKG_NAME= in installer")
68+
}
69+
70+
// Should contain the package's install.sh content (embedded).
71+
if !strings.Contains(body, "pkg_") {
72+
t.Error("installer should contain pkg_ functions from install.sh")
73+
}
74+
75+
t.Logf("installer size: %d bytes", len(body))
76+
}
77+
78+
// TestInstallerPowerShell verifies /api/installers/{pkg}.ps1 returns a PowerShell installer.
79+
func TestInstallerPowerShell(t *testing.T) {
80+
srv, ts := newTestServer(t)
81+
82+
pkg := "node"
83+
if srv.getPackage(pkg) == nil {
84+
t.Skipf("package %s not in cache", pkg)
85+
}
86+
87+
code, body := getWithUA(t, ts, "/api/installers/node@stable.ps1", "AMD64/unknown Windows/10.0.19045 msvc")
88+
if code != 200 {
89+
t.Fatalf("status %d: %s", code, body[:min(len(body), 500)])
90+
}
91+
92+
if !strings.Contains(body, "$Env:WEBI_VERSION") {
93+
t.Error("missing $Env:WEBI_VERSION in PS1 installer")
94+
}
95+
if !strings.Contains(body, "$Env:WEBI_PKG_URL") {
96+
t.Error("missing $Env:WEBI_PKG_URL in PS1 installer")
97+
}
98+
if !strings.Contains(body, "$Env:PKG_NAME") {
99+
t.Error("missing $Env:PKG_NAME in PS1 installer")
100+
}
101+
102+
t.Logf("PS1 installer size: %d bytes", len(body))
103+
}
104+
105+
// TestInstallerSelfHosted verifies selfhosted packages get a script without resolution.
106+
func TestInstallerSelfHosted(t *testing.T) {
107+
_, ts := newTestServer(t)
108+
109+
// ssh-utils is selfhosted — has install.sh but no releases.conf.
110+
code, body := getWithUA(t, ts, "/api/installers/ssh-utils.sh", "aarch64/unknown Darwin/24.2.0 libc")
111+
if code == 404 {
112+
t.Skip("ssh-utils not available as installer")
113+
}
114+
if code != 200 {
115+
t.Skipf("status %d (selfhosted may not render without cache): %s", code, body[:min(len(body), 200)])
116+
}
117+
118+
t.Logf("selfhosted installer size: %d bytes", len(body))
119+
}
120+
121+
// TestBootstrapUnknownPackage verifies 404 for unknown packages.
122+
func TestBootstrapUnknownPackage(t *testing.T) {
123+
_, ts := newTestServer(t)
124+
125+
code, _ := get(t, ts, "/nonexistent-package-xyz")
126+
if code != 404 {
127+
t.Errorf("expected 404, got %d", code)
128+
}
129+
}
130+
131+
// getWithUA fetches a URL with a custom User-Agent header.
132+
func getWithUA(t *testing.T, ts *httptest.Server, path, ua string) (int, string) {
133+
t.Helper()
134+
req, err := http.NewRequest("GET", ts.URL+path, nil)
135+
if err != nil {
136+
t.Fatalf("new request: %v", err)
137+
}
138+
req.Header.Set("User-Agent", ua)
139+
resp, err := http.DefaultClient.Do(req)
140+
if err != nil {
141+
t.Fatalf("GET %s: %v", path, err)
142+
}
143+
defer resp.Body.Close()
144+
body, _ := io.ReadAll(resp.Body)
145+
return resp.StatusCode, string(body)
146+
}

0 commit comments

Comments
 (0)