I'm looking to automate my build process using dagger. My build outputs multiple folders such as ./artifacts ./results etc. I'd like to ensure that all of them are returned to the host. I've tried Directory.export but it seems to have no effect. Can anyone explain to me how this should be done, preferably using the typescript SDK?
#Returning multiple folders to host
1 messages · Page 1 of 1 (latest)
your function should return a Directory or even better, a Changeset. Then when the end user calls that function, the CLI will automatically prompt the user to export the result to the local file system. The function itself can't export to your filesystem, it is sandboxed (if you call export inside the sandbox it actually exports to the sandbox filesystem)
is there an example documented somewhere?
I've tried with the Directory approach, however it didn't seem to work for me - I'm probably missunderstanding something fundamentally
could you share the code of your build function (redacted if necessary) and the command you use to run it?
sure, this is my latest attempt
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
export class NukeBuildOutput {
artifacts: Directory
results: Directory
constructor(artifacts: Directory, results: Directory) {
this.artifacts = artifacts;
this.results = results;
}
// ✅ Add a function to export both directories
@func()
async export(hostPath: string): Promise<string> {
// Export 'artifacts' to a subdirectory named 'artifacts' inside the hostPath
await this.artifacts.export(`${hostPath}/artifacts`);
// Export 'results' to a subdirectory named 'results' inside the hostPath
await this.results.export(`${hostPath}/results`);
// Return a message indicating success (or void, Dagger usually handles the success check)
return `Successfully exported artifacts and results to ${hostPath}`;
}
}
@object()
export class DaggerBuild {
@func()
async nukeBuild(
directory: Directory,
target: string = "All",
image: string = "mcr.microsoft.com/dotnet/sdk:10.0"): Promise<NukeBuildOutput> {
const container = dag
.container()
.from(image)
.withMountedDirectory("/app", directory)
.withWorkdir("/app")
.withExec(["dotnet", "tool", "restore"])
.withExec(["dotnet", "tool", "run", "nuke", "--target", target])
;
const results: Directory = container.directory("./results");
const artifacts: Directory = container.directory("./artifacts");
await results.entries();
await artifacts.entries();
return new NukeBuildOutput(artifacts, results);
}
}
I'm invoking this using: dagger call nuke-build --directory=. export --host-path=./output , however nothing is being created on my MacOS host
just to confirm, your goal is to create/update these directories in the caller's local directory right?
Do you also want to consume the build output programmatically? So 2 different uses right?
The answer to the 1st question is a definite "yes"
As to the 2nd question, I'm not yet sure how I can use it, but I suppose it wouldn't hurt - for learning's sake
@teal remnant I would start simple eg.
import {
dag,
object,
func,
argument,
Directory,
Container,
Changeset,
} from "@dagger.io/dagger"
@object()
export class Nuke {
@func()
async build(
@argument({ defaultPath: "." }) source: Directory,
base?: Container,
target: string = "All",
): Promise<Changeset> {
const container = (base ?? dag
.container()
.from("mcr.microsoft.com/dotnet/sdk:10.0"))
.withDirectory("/app", source)
.withWorkdir("/app")
.withExec(["dotnet", "tool", "restore"])
.withExec(["dotnet", "tool", "run", "nuke", "--target", target])
return container.directory("/app").changes(source)
}
}
to call: dagger call build
Ok, this is interesting and seems like a step in the right direction. The returned changeset contains all the files that I'd have expected to be created, however:
- I'm being prompted to toggle/submit/apply/discard (I've tried submitting and applying, I'm getting:
cannot diff with different relative paths: "/" != "/app"- not yet sure what the problem is)- Can the prompt be skipped/auto-accepted? I'm pretty sure this behavior would hang my CI
- You've added a Container argument to the
build()function, I'm assuming this enables some sort of chaining behavior? How does this work?
the container arg is just an improvement on your image arg. it allows passing a custom base image eg. dagger call build --base=myregistry.tld/dotnet/nuke:latest
still trying to wrap my head around the cannot diff with different relative paths: "/" != "/app" error. What is the dagger engine tryign to tell me?
It's a bug in Directory.changes() that makes it extra picky about its arguments. should be an easy fix
@teal remnant this fixes it:
import {
dag,
object,
func,
argument,
Directory,
Container,
Changeset,
} from "@dagger.io/dagger"
@object()
export class Nuke {
@func()
async build(
@argument({ defaultPath: "." }) source: Directory,
base?: Container,
target: string = "All",
): Promise<Changeset> {
const container = (base ?? dag.container().from("mcr.microsoft.com/dotnet/sdk:10.0").withWorkdir("/app"))
.withDirectory(".", source)
const before = container.directory(".")
const after = container
.withExec(["dotnet", "tool", "restore"])
.withExec(["dotnet", "tool", "run", "nuke", "--target", target])
.directory(".")
return after.changes(before)
}
}
this worked!~~ I'm still being prompted to accept the list of changes though. Can I auto-accept it somehow?~~ (found it, --auto-apply)
Also, it would appear that your usage of both Directory instances (before and after) is more like snapshots rather than filesystem references? Is my understanding correct?
Also, it would appear that your usage of both Directory instances (before and after) is more like snapshots rather than filesystem references? Is my understanding correct?
WDYM? It's doing a diff from the before and after FS snapshots based on the selected path
just thinking out loud trying to understand how this works. My initial impression was that a Directory is just a reference to a FS location, but the usage in the example above implies to me it's more like a snapshot (hence the diff between them) - is my understanding correct?
it's both. Directory is effectively a reference of a specific snapshot
i.e:
const ctr = dag.container().from("alpine")
const before = ctr.directory(".") // here `before` is a reference to the working-directory (".") of the snapshot `ctr`
const ctrAfterExec = ctr.withExec(["sh", "-c", "echo hello > hello.txt"])
const after = ctrAfterExec.directory(".") // here `after` is refernece to the working-directory (".") of the snapshot `ctrAfterExec`
let changeSet = before.changes(after) // changesets between `before` and `after`
LMK if that makes sense
it totally does, I'm going through the docs trying to find where is this behavior documented