#TypeScript SDK: weird `Directory` behaviour when serialized

1 messages · Page 1 of 1 (latest)

tribal shard
#

In my base class, I'm instantiating something that I call BuildEnvironment. It's an @object() that takes a Directory when instantiated and provides some functions to get different variants of my build environment (e.g. with one toolchain installed or another). While preparing those Containers, it is supposed to copy some files in the from the Directory.

Now, when invoked directly, it works just fine. I have it saved in a @func() env: BuildEnvironment in the root object, so I can invoke it via dagger call env base just fine.
Instead, when I pass it to a third class in the constructor (which is actually building something with those toolchains), the BuildEnvironment object complains that this.myDirectory.file is not a function. Hard-enough to debug, I ended up doing throw new Error(this.myDirectory) in the constructor and a quite long base64 string got printed. If I print its typeof, it is also string.

My guess, without knowing your engine codebase, is that the directory is serialized while being passed across objects, but on the deser phase it is not correctly restored as Directory, and just left as a string representing its runtime state.

Any clues?

round sequoia
#

Hey! could you share your full code example? It might be easier to reproduce and debug

tribal shard
#

Let me try to make a reduced version

#
import { dag, Container, Directory, object, func, argument } from "@dagger.io/dagger";

@object()
class BuildEnvironment {
  myDirectory: Directory;

  constructor(myDirectory: Directory) {
    this.myDirectory = myDirectory;
  }

  @func()
  base(): Container {
    return dag.container().from("alpine:latest").withFile("/x", this.myDirectory.file("test"));
  }
}

@object()
class Root {
  @func()
  env: BuildEnvironment;

  constructor(
    @argument({
      defaultPath: "/",
      ignore: ["**", "!test"],
    })
    myDirectory: Directory,
  ) {
    this.env = new BuildEnvironment(myDirectory);
  }

  @func()
  build() {
    return new Builder(this.env);
  }
}

@object()
class Builder {
  env: BuildEnvironment;

  constructor(env: BuildEnvironment) {
    this.env = env;
  }

  @func()
  something() {
    this.env.base().withExec(["echo", "/x"]);
  }
}
#

Assuming you have a file called test in your root project directory...

  • dagger call env base terminal works, and inside ls /x works
  • dagger call build something throws the error
round sequoia
tribal shard
#

First line of the Root class

round sequoia
tribal shard
#

The decoded content of the base64 string is


xxh3:c0171ee83939227a¡
xxh3:c0171ee83939227aß
    Directoryblob"S
digestI:Gsha256:0b5ba797e3df599b253e44c9613c6e6dc316f8ee5a8ff5deae0254e397ec1a9f":
    mediaType-:+application/vnd.oci.image.layer.v1.tar+zstd"
size(Ñ∫ú"Y
uncompressedI:Gsha256:e8526adac82280e59cb8b049fdbd8da23a5e02af5cfc9b60af405f91eaa3a6ceJxxh3:c0171ee83939227a
round sequoia
#

Thanks so much for the code, I think i fully understand the issue

With your code as is you can call this via the CLI and it works

levlaz@Levs-MacBook-Pro francesco_ts % dagger call env base with-exec --args "cat,/x" stdout 
✔ connect 0.4s
✔ initialize 0.9s
✔ prepare 0.0s
✔ root: Root! 2.9s
✔ Root.env: RootBuildEnvironment! 0.0s
✔ RootBuildEnvironment.base: Container! 7.8s
✔ Container.withExec(args: ["cat", "/x"]): Container! 0.2s
✔ Container.stdout: String! 0.2s

Full trace at https://dagger.cloud/levs-test-org/traces/127e90f48267ad18b542e9e0c830ca3c

this is a test

But for some reason its not working if you do the same exact thing via code. I am going to keep digging in but its possible @shy sundial may have an idea too

I suspect this has something to do with custom types https://docs.dagger.io/api/custom-types

A Dagger module can have multiple object types defined. It's important to understand that they are only accessible through chaining, starting from a function in the main object.

tribal shard
#

Glad to see it's reproducible! In the meantime I'll also try to see if constructing the BuildEnvironment in the Builder (by passing the directory directly to the Builder) makes the issue go away. If it does - then I think it really has to do with recursive deserialization of the types.

tribal shard
#
import { dag, Container, Directory, object, func, argument } from "@dagger.io/dagger";

@object()
class Root {
  myDirectory: Directory;

  constructor(
    @argument({
      defaultPath: "/",
      ignore: ["**", "!test"],
    })
    myDirectory: Directory,
  ) {
    this.myDirectory = myDirectory;
  }

  @func()
  build() {
    return new Builder(this.myDirectory);
  }
}

@object()
class BuildEnvironment {
  myDirectory: Directory;

  constructor(myDirectory: Directory) {
    this.myDirectory = myDirectory;
  }

  @func()
  base(): Container {
    return dag.container().from("alpine:latest").withFile("/x", this.myDirectory.file("test"));
  }
}

@object()
class Builder {
  @func()
  env: BuildEnvironment;

  constructor(myDirectory: Directory) {
    this.env = new BuildEnvironment(myDirectory);
  }

  @func()
  something() {
    this.env.base().withExec(["cat", "/x"]);
  }
}

reproducible also in this case.

  • dagger call build something -> fail
  • dagger call build env base with-exec --args cat,/x -> success
#

@round sequoia @shy sundial let me know how you intend to go forward here. Passing the --my-directory . argument also makes no difference.

shy sundial
#

Can you give me some log of the error you get?

That's a very interesting error case

tribal shard
shy sundial
#

Thanks a lot!

tribal shard
#

Do you have any guidance for me? I feel like I'm so close completing my pipeline with Dagger... such a pity to be stuck here 😄

shy sundial
#

I’m not sure about your issue actually, but try to go incrementally without sub object first, then add 1, your code is right but it seems it doesn’t serialize correctly your sub sub class