Skip to content
Open
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
22 changes: 21 additions & 1 deletion internal/backend/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,29 @@ func runBuiltin(ctx context.Context, invocation *builderInvocation) error {

func fetchURL(ctx context.Context, drv *zbstore.Derivation, realStoreDir string) error {
href := drv.Env["url"]
if href == "" {
var urls []string
if href != "" {
urls = append(urls, href)
}

hrefs := drv.Env["urls"]
urls = append(urls, strings.Fields(hrefs)...)
if len(urls) == 0 {
return fmt.Errorf("missing url environment variable")
}

var err error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the new internal/multierror package to collect all the errors to report.

for _, href := range urls {
err = getURL(ctx, drv, realStoreDir, href)
if err == nil {
break
}
}

return err
}

func getURL(ctx context.Context, drv *zbstore.Derivation, realStoreDir string, href string) error {
outputPath := drv.Env[zbstore.DefaultDerivationOutputName]
if outputPath == "" {
return fmt.Errorf("missing %s environment variable", zbstore.DefaultDerivationOutputName)
Expand Down
193 changes: 130 additions & 63 deletions internal/backend/realize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1150,76 +1150,143 @@ func TestRealizeCores(t *testing.T) {
}

func TestRealizeFetchURL(t *testing.T) {
ctx := testcontext.New(t)
dir := backendtest.NewStoreDirectory(t)

const fileContent = "Hello, World!\n"
mux := http.NewServeMux()
mux.HandleFunc("/hello.txt", func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, "hello.txt", time.Time{}, strings.NewReader(fileContent))
})
srv := httptest.NewServer(mux)
defer srv.Close()

exportBuffer := new(bytes.Buffer)
exporter := zbstore.NewExportWriter(exportBuffer)
const wantOutputName = "hello.txt"
wantOutputCA := nix.FlatFileContentAddress(mustParseHash(t, "sha256:c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31"))
drvContent := &zbstore.Derivation{
Name: wantOutputName,
Dir: dir,
Builder: "builtin:fetchurl",
System: "builtin",
Env: map[string]string{
"url": string(srv.URL + "/hello.txt"),
"out": zbstore.HashPlaceholder("out"),
tests := []struct {
name string
env func(string) map[string]string
wantOutputCA nix.ContentAddress
wantOutputName string
fileContent string
expectBuildFail bool
}{
{
name: "FetchSingularUrl",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env: func(urlBase string) map[string]string {
return map[string]string{
"url": urlBase + "/hello.txt",
"out": zbstore.HashPlaceholder("out"),
}
},
wantOutputCA: nix.FlatFileContentAddress(mustParseHash(t, "sha256:c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31")),
wantOutputName: "hello.txt",
fileContent: "Hello, World!\n",
},
Outputs: map[string]*zbstore.DerivationOutputType{
zbstore.DefaultDerivationOutputName: zbstore.FixedCAOutput(wantOutputCA),
{
name: "FetchBrokenUrls",
env: func(urlBase string) map[string]string {
return map[string]string{
"urls": "http://broken/hello.txt",
Comment thread
Abdiramen marked this conversation as resolved.
"out": zbstore.HashPlaceholder("out"),
}
},
wantOutputCA: nix.FlatFileContentAddress(mustParseHash(t, "sha256:c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31")),
wantOutputName: "hello.txt",
fileContent: "Hello, World!\n",
expectBuildFail: true,
},
{
name: "FetchNoUrl",
env: func(urlBase string) map[string]string {
return map[string]string{}
},
wantOutputCA: nix.FlatFileContentAddress(mustParseHash(t, "sha256:c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31")),
wantOutputName: "hello.txt",
fileContent: "Hello, World!\n",
expectBuildFail: true,
},
{
name: "FetchFromUrlsList",
env: func(urlBase string) map[string]string {
return map[string]string{
"urls": fmt.Sprintf("%s, %s", urlBase+"/bad/hello.txt", urlBase+"/hello.txt"),
"out": zbstore.HashPlaceholder("out"),
}
},
wantOutputCA: nix.FlatFileContentAddress(mustParseHash(t, "sha256:c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31")),
wantOutputName: "hello.txt",
fileContent: "Hello, World!\n",
},
}
drvPath, _, err := storetest.ExportDerivation(exporter, drvContent)
if err != nil {
t.Fatal(err)
}
if err := exporter.Close(); err != nil {
t.Fatal(err)
}

_, client, err := backendtest.NewServer(ctx, t, dir, &backendtest.Options{
TempDir: t.TempDir(),
})
if err != nil {
t.Fatal(err)
}
codec, releaseCodec, err := storeCodec(ctx, client)
if err != nil {
t.Fatal(err)
}
err = codec.Export(nil, exportBuffer)
releaseCodec()
if err != nil {
t.Fatal(err)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := testcontext.New(t)
dir := backendtest.NewStoreDirectory(t)

realizeResponse := new(zbstorerpc.RealizeResponse)
err = jsonrpc.Do(ctx, client, zbstorerpc.RealizeMethod, realizeResponse, &zbstorerpc.RealizeRequest{
DrvPaths: []zbstore.Path{drvPath},
})
if err != nil {
t.Fatal("build drv:", err)
}
got, err := backendtest.WaitForSuccessfulBuild(ctx, client, realizeResponse.BuildID)
if err != nil {
gotLog, _ := backendtest.ReadLog(ctx, client, realizeResponse.BuildID, drvPath)
t.Fatalf("build drv: %v\nlog:\n%s", err, gotLog)
}
mux := http.NewServeMux()
mux.HandleFunc(
"/hello.txt",
func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(
w,
r,
"hello.txt",
time.Time{},
strings.NewReader(test.fileContent))
})

srv := httptest.NewServer(mux)
defer srv.Close()

wantOutputPath, err := zbstore.FixedCAOutputPath(dir, wantOutputName, wantOutputCA, zbstore.References{})
if err != nil {
t.Fatal(err)
exportBuffer := new(bytes.Buffer)
exporter := zbstore.NewExportWriter(exportBuffer)
drvContent := &zbstore.Derivation{
Name: test.wantOutputName,
Dir: dir,
Builder: "builtin:fetchurl",
System: "builtin",
Env: test.env(srv.URL),
Outputs: map[string]*zbstore.DerivationOutputType{
zbstore.DefaultDerivationOutputName: zbstore.FixedCAOutput(test.wantOutputCA),
},
}
drvPath, _, err := storetest.ExportDerivation(exporter, drvContent)
if err != nil {
t.Fatal(err)
}
if err := exporter.Close(); err != nil {
t.Fatal(err)
}

_, client, err := backendtest.NewServer(ctx, t, dir, &backendtest.Options{
TempDir: t.TempDir(),
})
if err != nil {
t.Fatal(err)
}
codec, releaseCodec, err := storeCodec(ctx, client)
if err != nil {
t.Fatal(err)
}
err = codec.Export(nil, exportBuffer)
releaseCodec()
if err != nil {
t.Fatal(err)
}

realizeResponse := new(zbstorerpc.RealizeResponse)
err = jsonrpc.Do(ctx, client, zbstorerpc.RealizeMethod, realizeResponse, &zbstorerpc.RealizeRequest{
DrvPaths: []zbstore.Path{drvPath},
})
if err != nil {
t.Fatal("build drv:", err)
}
// expect build to fail
got, err := backendtest.WaitForSuccessfulBuild(ctx, client, realizeResponse.BuildID)
if err != nil && test.expectBuildFail {
return
}
if err != nil {
gotLog, _ := backendtest.ReadLog(ctx, client, realizeResponse.BuildID, drvPath)
t.Fatalf("build drv: %v\nlog:\n%s", err, gotLog)
}
Comment on lines +1275 to +1281

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would still be good to show the log anyway.

Suggested change
if err != nil && test.expectBuildFail {
return
}
if err != nil {
gotLog, _ := backendtest.ReadLog(ctx, client, realizeResponse.BuildID, drvPath)
t.Fatalf("build drv: %v\nlog:\n%s", err, gotLog)
}
if err != nil {
gotLog, _ := backendtest.ReadLog(ctx, client, realizeResponse.BuildID, drvPath)
t.Logf("build drv: %v\nlog:\n%s", err, gotLog)
if !test.expectBuildFail {
t.Fail()
}
return
}


wantOutputPath, err := zbstore.FixedCAOutputPath(dir, test.wantOutputName, test.wantOutputCA, zbstore.References{})
if err != nil {
t.Fatal(err)
}
checkSingleFileOutput(t, drvPath, wantOutputPath, []byte(test.fileContent), got)
})
}
checkSingleFileOutput(t, drvPath, wantOutputPath, []byte(fileContent), got)
}

func TestRealizeSignature(t *testing.T) {
Expand Down
16 changes: 13 additions & 3 deletions internal/frontend/prelude.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@ local function baseNameOf(path)
return base
end

---@param args {url: string, hash: string, name: string?, executable: boolean?}
---@param args {hash: string, name: string?, executable: boolean?, url: string?, urls: string[]?}
---@return derivation
function fetchurl(args)
local name = args.name or baseNameOf(args.url)
if (args.url == nil or args.url == "") and (args.urls == nil or next(args.urls) == nil) then
error("Either url or urls must be set")
end

local name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarity, let's grab args.name first and then check the fallback. It was a little hard to see that behavior on first read.

Suggested change
local name
local name = args.name
if not name then

if args.url == nil then
name = args.name or baseNameOf(args.urls[1])
else
name = args.name or baseNameOf(args.url)
end
Comment on lines +26 to +30

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, for clarity, let's flip this condition.

Suggested change
if args.url == nil then
name = args.name or baseNameOf(args.urls[1])
else
name = args.name or baseNameOf(args.url)
end
if args.url then
name = baseNameOf(args.url)
else
name = baseNameOf(args.urls[1])
end


local outputHashMode = "flat"
if args.executable then
outputHashMode = "recursive"
Expand All @@ -29,7 +39,7 @@ function fetchurl(args)
system = "builtin";

url = args.url;
urls = { args.url };
urls = args.urls;
executable = args.executable or false;
unpack = false;
outputHash = args.hash;
Expand Down
Binary file modified internal/frontend/prelude.luac
Binary file not shown.
2 changes: 1 addition & 1 deletion zb_defs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function storePath(path) end
function toFile(name, s) end

---Create a derivation that downloads a URL.
---@param args {url: string, hash: string, name: string?, executable: boolean?}
---@param args {hash: string, name: string?, executable: boolean?, url: string?, urls: string[]?}
---@return derivation
function fetchurl(args) end

Expand Down
Loading