Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 23 additions & 3 deletions internal/backend/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const (
func runBuiltin(ctx context.Context, invocation *builderInvocation) error {
switch invocation.derivation.Builder {
case builtinBuilderPrefix + "fetchurl":
if err := fetchURL(ctx, invocation.derivation, invocation.realStoreDir); err != nil {
if err := fetchURLs(ctx, invocation.derivation, invocation.realStoreDir); err != nil {
fmt.Fprintf(invocation.logWriter, "%s: %v\n", invocation.derivation.Builder, err)
return builderFailure{fmt.Errorf("%s failed", invocation.derivation.Builder)}
}
Expand All @@ -52,11 +52,31 @@ func runBuiltin(ctx context.Context, invocation *builderInvocation) error {
}
}

func fetchURL(ctx context.Context, drv *zbstore.Derivation, realStoreDir string) error {
func fetchURLs(ctx context.Context, drv *zbstore.Derivation, realStoreDir string) error {
Comment thread
Abdiramen marked this conversation as resolved.
Outdated
href := drv.Env["url"]
if href == "" {
hrefs := drv.Env["urls"]
var urls []string
if href == "" && hrefs != "" {
urls = strings.Split(hrefs, " ")
Comment thread
Abdiramen marked this conversation as resolved.
Outdated
} else if href != "" && hrefs == "" {
urls = []string{href}
} else {
Comment thread
Abdiramen marked this conversation as resolved.
Outdated
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 i := 0; i < len(urls); i++ {
href = urls[i]
Comment thread
Abdiramen marked this conversation as resolved.
Outdated
err = fetchURL(ctx, drv, realStoreDir, href)
if err == nil {
break
}
}

return err
}

func fetchURL(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
192 changes: 129 additions & 63 deletions internal/backend/realize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1150,76 +1150,142 @@ 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: "Fetch singular url",
Comment thread
Abdiramen marked this conversation as resolved.
Outdated
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: "Fetch broken urls",
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: "Fetch no url(s)",
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: "Fetch from a list of urls",
env: func(urlBase string) map[string]string {
return map[string]string{
"urls": fmt.Sprintf("http://broken/hello.txt, %s", 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
20 changes: 15 additions & 5 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 most be set")
Comment thread
Abdiramen marked this conversation as resolved.
Outdated
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 @@ -28,8 +38,8 @@ function fetchurl(args)
builder = "builtin:fetchurl";
system = "builtin";

url = args.url;
urls = { args.url };
url = args.url or "";
Comment thread
Abdiramen marked this conversation as resolved.
Outdated
urls = args.urls;
executable = args.executable or false;
unpack = false;
outputHash = args.hash;
Expand Down Expand Up @@ -116,7 +126,7 @@ end
function fetchArchive(args)
local name = args.name or baseNameOf(args.url)
local dl = fetchurl {
url = args.url;
urls = { args.url };
Comment thread
Abdiramen marked this conversation as resolved.
Outdated
hash = args.hash,
name = name,
}
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