#python envs

1 messages Β· Page 1 of 1 (latest)

timid seal
#

While you're here @fringe mountain πŸ™‚ Did you happen to ever start working on the changes to the python SDK for the new way of building environments? i.e. with_checks, with_command, etc.? Not expecting you to have since obviously we've all been focusing on other stuff the last 2 weeks, but just want to check in case.

Asking because having multiple SDKs in the demos next week would be super cool. Either way, I know you're super busy with a million other things so I may take a look at the python sdk code and see if I can manage to throw something together quickly too. Just want to a make sure I'm not duplicating anything you've already started there

fringe mountain
#

Yeah, I've been trying to get into it for the past 2 weeks but got pushed into other things. Every day since the end of last week it's like "let me finish this off today so I can focus on zenith". Over the weekend I thought monday was the day πŸ˜…

#

Yeah, I just have to port 3 samples in go to the other sdks for error handling tomorrow and I'll jump right into zenith 🀞

#

Everything's ready for the release otherwise.

#

This is my stash πŸ™‚

#

July 18

timid seal
# fringe mountain Yeah, I've been trying to get into it for the past 2 weeks but got pushed into o...

No worries in the slightest, I 100% get that πŸ™‚

Yeah, I just have to port 3 samples in go to the other sdks for error handling tomorrow and I'll jump right into zenith
If you're not sacrificing other higher priorities and/or time from life, that sounds great! Like I said, I am totally willing to give it a shot too, or to finish fleshing out anything you have time to write, it's just meant for a demoable POC, so my Python skills may be just barely good enough to get by for that

fringe mountain
#

This is like thinking with code more than anything that's working 😁

timid seal
fringe mountain
fringe mountain
#

@timid seal, if I change something in universe/dagger/gosdk, do I need to hack/dev for the universe.tar?

#

Or is that just to say dagger check -e dagger-gosdk (without path)?

timid seal
# fringe mountain Or is that just to say `dagger check -e dagger-gosdk` (without path)?

Yeah exactly, embedding universe just enables the loadFromUniverse api: https://github.com/dagger/dagger/blob/3de7d5673051084ddb471942f31465bef469bc88/core/schema/environment.graphqls#L45

But you don't use that api when just loading direct from a local dir or git repo

GitHub

A programmable CI/CD engine that runs your pipelines in containers - dagger/dagger

fringe mountain
#

It's taking so long to dagger check -e ./universe/dagger/gosdk.

#

The progress output in a single line makes me wonder if it's stuck in a loop.

#

dagger check --help doesn't work

#
❯ dagger check --help
session client: server connection closed <nil>
[0.01s] ERROR dagger check --help
[0.01s] connecting to Dagger
[0.00s] loading environment
WARNING: Using development engine; skipping version compatibility check.
β€’ Engine: 918e5562f367 (version devel ())
Error: failed to get environment root dir: failed to read local config file: open /Users/helder/Projects/dagger/dagger.json: no such file or directory
#

How do I use the unfocused progress?

timid seal
#

I just tried and got the same thing, and my computer's fan also turned on, which pretty much never happens, so yeah there's some infinite loop for some reason...

fringe mountain
#

Do you need list instead of defaulting to listing?

#

Ah

❯ dagger check -e ./universe/dagger/gosdk list
[47.0s] list checks
[47.0s] list checks
check name  description
goLint      Lint the Dagger Go SDK
TODO: once namespacing is in place, can just name this "Lint"
WARNING: Using development engine; skipping version compatibility check.
β€’ Engine: 918e5562f367 (version devel ())
#

That devel warning is annoying.

#

Hmmm... this will require some changing:

check := defaultContext.Client().EnvironmentCheck()

The Python dagger types aren't supposed to be instantiated without a context, which is passed around internally. It's created during connection.

#

Maybe client.environment_check(). I see it's optional.

#

Ah:

check = client.environment_check().with_name(name)
timid seal
#

dagger --progress=plain check -e ./universe/dagger/gosdk shows it with full progress output, it's definitely just an infinite recursion, most likely in the CLI itself. I'd bet probably it's somehow accidentally creating a cobra command that executes itself πŸ˜„

fringe mountain
#

Yeah, I can list, but not run.

#

I'm going to go have dinner. Will be able to unblock this today?

#

Or is there another working type that I can work on?

timid seal
timid seal
fringe mountain
#

Noice πŸ˜ƒ

fringe mountain
timid seal
fringe mountain
#

I was returning a bool but now I see: Error: failed to get check result: input:1: environmentCheck.result failed to resolve field py_lint: expected string output for check entrypoint, so I'm looking for the requirements as I go along πŸ™‚ Need to return str.

#

Can you clarify what needs to be in /outputs/dagger.json for a check? A string for an error and if empty it succeeded?

timid seal
fringe mountain
#

I just returned "Hello world"

#

But how does it fail?

timid seal
#

Ah okay, so you should return the ID for the EnvironmentCheck . In the go sdk there's this line where we get the object we will json marshal and then write to outputs.json: https://github.com/sipsma/dagger/blob/de79dd88ed8af9f0035435fd225e8fccb90b354b/sdk/go/server.go#L112-L112

It checks to see if the object being provided is "id-able" and if so, returns the id for the object instead of the object itself: https://github.com/sipsma/dagger/blob/de79dd88ed8af9f0035435fd225e8fccb90b354b/sdk/go/server.go#L602-L602

timid seal
# fringe mountain But how does it fail?

We were still trying to figure out how to best expose that, but iirc right now it shows up as failed when the actual environment execution fails, i.e. in go the code returns an error and this branch is followed: https://github.com/sipsma/dagger/blob/de79dd88ed8af9f0035435fd225e8fccb90b354b/sdk/go/server.go#L105-L105

GitHub

A portable devkit for CI/CD pipelines. Contribute to sipsma/dagger development by creating an account on GitHub.

#

Then I added some magic for catching that in the gql server-side and turning it into the actual failed check

fringe mountain
#

Oh, so the server expects the runtime to write an error to stderr and exit code 1?

timid seal
#

wait... sorry I need to go back and check the code some more, I'm switching contexts from cache problems and might be misremembering stuff since it was a few weeks ago now, one min

timid seal
# fringe mountain Oh, so the server expects the runtime to write an error to stderr and exit code ...

Okay, yeah it's handled server side here: https://github.com/sipsma/dagger/blob/de79dd88ed8af9f0035435fd225e8fccb90b354b/core/schema/env.go#L192-L192 it sees the resolver returned an error and sets the success to false

GitHub

A portable devkit for CI/CD pipelines. Contribute to sipsma/dagger development by creating an account on GitHub.

timid seal
# fringe mountain

The output needs a lot of work obviously, but yeah I think you are getting the expected output there

fringe mountain
#

Ok, cool!

#

Cool how we can proxy that

#

Doing some general error handling removes boilerplate for users

fringe mountain
#

@timid seal What's the priority you need me to support in Python?

timid seal
#

I'm working out the exact details still, but I'm trying to minimize what we need for the demo from python just to save time (it's easier for me to do stuff quick in Go rather than do a mind transfer from to you and do it in python).

At the moment I think the only extra thing may end up being artifacts, but I haven't added that schema yet. It also should be extremely similar to supporting any of the other entrypoint types (in Go I'm pretty much just copy-pasting a ton, will go back later to de-dupe probably hundreds of lines of code)

fringe mountain
#

Ok, I'll add commands so there's checks and commands. Should be easy to add more afterwords.

#

Btw there's no more need to pass around the client:

timid seal
timid seal
fringe mountain
#

Do I push to your PR or is it better to make a PR for the PR? πŸ™‚

#

I'll just push.

fringe mountain
timid seal
# fringe mountain I'll just push.

Yeah just push to that PR for now. My idea is that we can continue using that monolith PR to assemble demo code, but now that the re-arch PR is merged we aren't blocked anymore and can instead merge the rest of Zenith piece by piece, so once demo stuff is settled I'm gonna start breaking everything down into smaller PRs that are actually mergable

tough moon
#

!!! ❀️

fringe mountain
#

I want to have a env = dagger.Environment() instead of from dagger.server import Environment but there's a conflict. Not sure what other name I could use. At least .server should be renamed but don't know to what.

#

Maybe ext or env?

#
from dagger.env import Environment, Check, Command
#

@timid seal parent in inputs will only be set when we have namespacing right? When the field is no longer in Query.

timid seal
fringe mountain
#

Ok. I'm also ready to push πŸ™‚ I'll do it after you.

#

Are you using interfaces for the namespacing then?

timid seal
# fringe mountain Are you using interfaces for the namespacing then?

No not yet, just an incremental step towards that. Whereas before we were stitching each individual command/check/shell/etc. right under Query, now we stitch in

extend Type Query {
  <environment name from dagger.json>: <capitalized environment name from dagger.json>
}

type <capitalized environment name from dagger.json> {
   ... (fields for each command/check/shell/etc.)
}
timid seal
# fringe mountain Ok. I'm also ready to push πŸ™‚ I'll do it after you.

Okay just pushed that. Also btw, you'll see from my commit yesterday there's a bunch of dirs under universe prefixed w/ _. That's a temporary hack because yesterday I made a change to load all of universe when the engine server starts, but there were some updates needed to get the pre-existing ones compiling again. You can still run e.g. ./hack/dev ./bin/dagger shell --env ./universe/_dagger dev-shell though

#

I'm gonna fix all that right now

fringe mountain
#

Yep πŸ‘

#

@timid seal I'm going to push what I have now.

#

Checks don't support flags yet, I'm going to prioritize having another resolver, and then do flags for both.

timid seal
#

only conflicts might be due to renaming that dir

fringe mountain
#

Hey @timid seal, still trying to debug... stuck on "client closed".... I tracked it down to the image pull in the python runtime. πŸ˜•

fringe mountain
fringe mountain
#

Ok, that fixed it.

#

Now I'm getting this again:

Error: failed to get check result: input:1: environmentCheck.result failed to resolve field goLint: failed to execute check: failed to get runtime container for fieldResolver: client closed
#

Restarting docker fixes it, but it comes back after a second run. Maybe there's a leak somewhere.

#

I can only run one command and then I have to restart the engine container.

#

@timid seal sorry, I pushed prematurely.

fringe mountain
#

@timid seal, if I add a second check to pysdk, the API calls only py_lint. dagger check list lists both, but dagger check py_test runs lint. dagger check py_lint it's the same.

#

I realize the "client closed" issue is due to a "401 unauthorized" while pulling from docker hub after a second dagger check.

#

Sorry for bombarding you but nothing's working today πŸ€ͺ

timid seal
#

Thank you for the fix here! https://github.com/dagger/dagger/pull/5443/commits/2a89f482ef5ed02fb098354d8ab06f2ee4f012ec

I can run the codegen now to get bindings for the python env generated into universe/_demo/dagger.gen.go. I am also seeing the same behavior where it runs both checks unexpectedly; i.e. ./hack/dev ./bin/dagger check --progress=plain -e ./universe/_demo/client unit-test runs both publish and unit-test checks... Which isn't good but I suppose better than not running any checks πŸ™ƒ

#

Looking into that, I think I need to just do another pass on the check stuff as a whole, there's lots of weird behavior all over

fringe mountain
#

Man, I'm spending so much time rebuilding, rebuilding, wiping everything, rebuild again... and it takes so long

#

An error when running /entrypoint -schema is killing the engine:

time="2023-08-09T15:24:03Z" level=fatal msg="FAILED TO LOAD UNIVERSE: failed to load environment daggerpython: failed to load environment: failed to get envid file: process \"/entrypoint -schema\" did not complete successfully: exit code: 1" client_hostname=Sirius.local client_id=ptcyorf0fy8qn5fjnr6zf2sgw register_client=false server_id=qq78n2rkjv86qdvrogl9qv8d0 spanID=c96db7e7b65e7b9a traceID=edd9ad5c5103a1d172eb6520498f33ee

Then when I restart and run again it says there's leftover networks from last run and dies again.

timid seal
timid seal
fringe mountain
#

Do you run with ./hack/dev everytime?

timid seal
#

Just pushed include/exclude support to the branch, it saves 10+ seconds per dev loop iteration for me, so still a ways to go but should help

fringe mountain
#

I'm running an env inside the sdk dir, so outside universe, with root to ../.., but it's failing due to universe having failed to load which was burnt into the dev engine. I didn't expect this to have influence but it's the kind of thing that puts me in the rat wheel. Many times I don't even realize I'm getting stale outputs, that's what pushed me to wipe more frequently (today).

timid seal
fringe mountain
#

Not random. I made a few changes which broke -schema but that's normal because how would I test it without running? So the runtime then fails which fails the whole universe. What I get confused on is how a change in the sdk changes the universe. Since a few hours ago I started iterating on an env inside sdk/python/experimental, so not inside universe.

#

I don't run ./hack/dev bash, I have direnv with the vars from ./hack/dev so that I can just dagger check .... When I want to rebuild I just do ./hack/dev (nothing else), and go back to simple dagger .... So, not a subshell. Is that ok?

timid seal
fringe mountain
#

Hmmm... so in dagger.json i have root: ../../ which points to sdk/python, but I'm getting: /Users/helder/Projects/dagger/sdk/python/src/dagger/experimental/pysdk/dagger.json: no such file or directory. I believe I saw a todo that root was temporarily assumed to be blank. For now I can't run an env without uploading the entire repo?

timid seal
#

I would have thought that you could get away with just uploading the python repo if your env is itself python, but wondering also if the pythonruntime code doesn't handle that well at the moment

fringe mountain
#

I'm just running an env inside sdk/python:

sdk/python$ dagger check -e ./experimental/pysdk list
#

Man, today hasn't been easy. I'm getting this all the time:

dial unix /run/buildkit/buildkitd.sock: connect: no such file or directory

Just doing ./hack/dev

#

Then I get it to build, run a check list and I get this:

7: exec /entrypoint -schema DONE
Error: load universe: make request: Post "http://dagger/query": rpc error: code = Unavailable desc = error reading from server: command [docker exec -i dagger-engine.dev buildctl dial-stdio] has exited with exit status 137, please make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=
timid seal
# fringe mountain Man, today hasn't been easy. I'm getting this all the time: ``` dial unix /run/b...

Sorry ☹️ I have been dealing with slowness and the bugs around checks, but it seems like you have been hitting a whole other category of problems... I am still wondering if something is just broken particularly on macos/docker desktop, I don't know what else the difference would be. When you get that dial unix ... error, if you check docker logs dagger-engine.dev do you see any stack traces? Is it crashing somehow? Also worth checking if docker desktop is running out of disk space or something, could try docker system prune -a and/or docker volume prune -a

fringe mountain
#

Yeah, the problem is here:

FAILED TO LOAD UNIVERSE: failed to load environment daggerpython: failed to load environment: failed to build schema: command \"py_publish\" has no result type

This crashed the engine and this output didn't show in the console, just the docker logs.

#

I just introduced commands and added a @env.command function. So I guess I need to set .with_result_type or something.

#

I'll figure it out, focus on what you need to do πŸ™‚

#

Sorry @timid seal, but what are the acceptable values for resultType?

#

GraphQL types as strings?

#

So "String" for strings, right?

timid seal
fringe mountain
#

Ok

timid seal
#

I also just noticed that when I don't run ./hack/dev I get weird errors trying to run something from universe, so I'll squash whatever bug is causing that after the meeting

timid seal
fringe mountain
#

I can push working command, as I said in the beginning of the meet. There's an issue with naming. If I name it py_lint, I think the API is adding it as pyLint, the cli is putting it as py-lint (all that is good), but I think the resolver in the runtime is then pyLint which doesn't exist in the python env. This seems to happen on command, not on check. So I need to normalize it to and from graphql convention here.

#

Ah, just saw the "various fixes" commit (in ListChecks):

+        name = strcase.ToKebab(name)

😁

#

Can I push? I don't want to cross wires.

timid seal
#

But that's totally separate

timid seal
fringe mountain
#

Did this affect the client closed problem?

#

401 in pull

timid seal
fringe mountain
#
19: ./bin/dagger do --debug --progress=plain -e ./universe/dagger/pysdk py-publish
19: [2.51s] Running command "py-publish"...
19: [2.51s] Error: make request: input:3: Cannot query field "pyPublish" on type "Daggerpython". Did you mean "PyPublish"?
#

I'm setting .with_name("pyPublish")

#

The list shows py-publish.

#

Seems like dagger check unit-test works, so it's just dagger do.

timid seal
# fringe mountain ```console 19: ./bin/dagger do --debug --progress=plain -e ./universe/dagger/pys...

Running off the current zenith branch, ./hack/dev ./bin/dagger do --debug --progress=plain -e ./universe/dagger/pysdk --help gives me

19: [0.33s] Usage:
19: [0.33s]   dagger do [flags]
19: [0.33s]   dagger do [command]
19: [0.33s] 
19: [0.33s] Available Commands:
19: [0.33s]   pypublish   Publish the client.
19: [0.33s] 
19: [0.33s] Flags:
19: [0.33s]   -e, --env string      Path to dagger.json config file for the environment or a directory containing that file. Either local path (e.g. "/path/to/some/dir") or a git repo (e.g. "git://github.com/dagger/dagger?ref=branch?subpath=path/to/s
ome/dir"). (default ".")
19: [0.33s]       --focus           Only show output for focused commands. (default true)
19: [0.33s]   -h, --help            help for do
19: [0.33s]   -o, --output string   If the command returns a file or directory, it will be written to this path. If --output is not specified, the file or directory will be written to the environment's root directory when using a environment loaded f
rom a local dir.
19: [0.33s] 
19: [0.33s] Global Flags:
19: [0.33s]       --cpuprofile string   collect CPU profile to path, and trace at path.trace
19: [0.33s]       --debug               Show more information for debugging
19: [0.33s]       --pprof string        serve HTTP pprof at this address
19: [0.33s]       --progress string     progress output format (auto, plain, tty) (default "auto")
19: [0.33s]   -s, --silent              disable terminal UI and progress output
19: [0.33s]       --workdir string      The host workdir loaded into dagger (default ".")
19: [0.33s] 
19: [0.33s] Use "dagger do [command] --help" for more information about a command.
19: [0.33s]

Then if I do ./hack/dev ./bin/dagger do --debug --progress=plain -e ./universe/dagger/pysdk pypublish it seems to work as expected, runs successfully.

Did you re-run ./hack/dev after changing stuff in universe? My change earlier doesn't result in it always being dynamically reloaded, just fixes that error about client being closed. So you still need a ./hack/dev after any changes

fringe mountain
#

I'm making changes that you don't have. Namely I'm renaming to camelCase when registering resolvers/functions.

#

So I'm specifically naming py_publish to see this all work out.

fringe mountain
#

So I expect py_publish to be saved as a pyPublish field in the API; for the CLI to see py-publish; and for when it gets invoked to map pyPublish back to the py_publish function.

#

So this works:

./hack/dev ./bin/dagger check --debug --progress=plain -e ./universe/_demo/client unit-test

But not this:

./hack/dev ./bin/dagger do --debug --progress=plain -e ./universe/_demo/client py-publish
#

Maybe it's the order of the flags?

#
19: ./bin/dagger --debug --progress=plain -e ./universe/_demo/client do py-publish
19: [3.34s] Usage:
19: [3.34s]   dagger do [flags]
19: [3.34s]   dagger do [command]
19: [3.34s]
19: [3.34s] Available Commands:
19: [3.34s]   my-publish  Publish the client
19: [3.34s]
19: [3.34s] Flags:
19: [3.34s]   -e, --env string      Path to dagger.json config file for the environment or a directory containing that file. Either local path (e.g. "/path/to/some/dir") or a git repo (e.g. "git://github.com/dagger/dagger?ref=branch?subpath=path/to/some/dir"). (default ".")
19: [3.34s]       --focus           Only show output for focused commands. (default true)
19: [3.34s]   -h, --help            help for do
19: [3.34s]   -o, --output string   If the command returns a file or directory, it will be written to this path. If --output is not specified, the file or directory will be written to the environment's root directory when using a environment loaded from a local dir.
19: [3.34s]
19: [3.34s] Global Flags:
19: [3.34s]       --cpuprofile string   collect CPU profile to path, and trace at path.trace
19: [3.34s]       --debug               Show more information for debugging
19: [3.34s]       --pprof string        serve HTTP pprof at this address
19: [3.34s]       --progress string     progress output format (auto, plain, tty) (default "auto")
19: [3.34s]   -s, --silent              disable terminal UI and progress output
19: [3.34s]       --workdir string      The host workdir loaded into dagger (default ".")
19: [3.34s]
19: [3.34s] Use "dagger do [command] --help" for more information about a command.
19: [3.34s]
#

Ah... silly me.

#

I renamed it to my_publish to disambiguate with another env.

#

my β‰  py facepalm

#
19: ./bin/dagger do --debug --progress=plain -e ./universe/_demo/client my-publish
19: [5.64s] Python 3.11.1
#

Going to push.

#

By the way, is py_lint still needed? vs just lint in dagger/pysdk?

timid seal
timid seal
fringe mountain
#

It's much smoother now

timid seal
#

On a tangential note, I'm doing another pass on the whole checks API as part of fixing them. I'm only slightly changing the actual api:

diff --git a/core/schema/environment.graphqls b/core/schema/environment.graphqls
index e8258fad..c93083dc 100644
--- a/core/schema/environment.graphqls
+++ b/core/schema/environment.graphqls
@@ -261,7 +261,7 @@ type EnvironmentCheck {
   setStringFlag(name: String!, value: String!): EnvironmentCheck!
 
   "TODO"
-  result: [EnvironmentCheckResult!]
+  result: EnvironmentCheckResult!
 }
 
 "A flag accepted by a environment check."
@@ -275,8 +275,16 @@ type EnvironmentCheckFlag {
 
 "TODO"
 type EnvironmentCheckResult {
+  name: String!
+  withName(name: String!): EnvironmentCheckResult!
+
   success: Boolean!
+  withSuccess(success: Boolean!): EnvironmentCheckResult!
+
   output: String!
-  name: String!
+  withOutput(output: String!): EnvironmentCheckResult!
+
+  subresults: [EnvironmentCheckResult!]
+  withSubresult(id: EnvironmentCheckID!): EnvironmentCheckResult!
 }

And then on the SDK side the new rule is that:

  1. The resolver must return an EnvironmentCheckResult
  2. If the resolver returns an error (or in python an exception is thrown), that's not interpreted as a check failure but a runtime error (so check is in a unknown state)

Sketch of what that'll look like for various use cases in Go SDK:

func main() {
    DaggerClient().Environment().
        WithCheck_(UnitTest).
        WithCheck_(IntegTest).
        WithCheck_(TODOTest).
        Serve()
}

func UnitTest(ctx dagger.Context) (*dagger.EnvironmentCheckResult, error) {
    // TODO: hypothetical example of creating check from subchecks of other envs
    return DaggerClient().EnvironmentCheck().
        WithSubcheck(DaggerClient().Democlient().UnitTest()).
        WithSubcheck(DaggerClient().Demoserver().UnitTest()).
        Result(), nil
}

func IntegTest(ctx dagger.Context) (*dagger.EnvironmentCheckResult, error) {
    // TODO: clientApp := Dagger().Democlient().Build()
    clientApp := DaggerClient().Apko().Wolfi([]string{"curl"})

    // TODO: need combined stdout/stderr really badly now
    stdout, err := clientApp.
        WithServiceBinding("server", DaggerClient().Demoserver().Container()).
        WithExec([]string{"curl", "http://server:8081/hello"}).
        Stdout(ctx)
    // TODO: this is all boilerplatey, sdk-specific helpers will improve
    if err != nil {
        return DaggerClient().EnvironmentCheckResult().
            WithSuccess(false).
            WithOutput(err.Error()), nil
    }
    return DaggerClient().EnvironmentCheckResult().
        WithSuccess(true).
        WithOutput(stdout), nil
}

func TODOTest(ctx dagger.Context) (*dagger.EnvironmentCheckResult, error) {
    // TODO: delete, just hypothetical example of using universe env to dynamically generate checks
    return DaggerClient().Go().TestsFrom(DaggerClient().Host().Directory(".")).Result(), nil
}

The IntegTest example is particularly ugly relative to before, but it can be addressed by adding sdk-specific helpers as needed. That code is basically what a helper would internally be doing.

There are just way too many problems and ambiguities with the existing check implementation, especially around what is test output, what does an error mean, etc. I think this approach will result in more consistent comprehensible behavior. Though I am still in the middle of implementing so still subject to change πŸ˜…

#

Just want to double check with you that this will work in Python also. I am more than happy to give a go at it once I finish the go sdk side of things, just checking ahead of time if you see any gotchas

fringe mountain
#

You can break Python and I'll fix soon after. If you give an heads up on what needs to change that's a great help πŸ™‚

#

Those conditions don't seem right. It's the same as we have now. Except that the error is catched SDK side instead of API side. You're thinking runing the integTest could fail from somewhere else, even before executing the function?

#

It would have to be an SDK error, since running the entrypoint until calling the function.

timid seal
#

The problem right now is that the behavior gets really confusing once you start thinking about functions that are actually executing the check vs functions that are just assembling subchecks and calling them, etc. It's way simpler to just have the function return the check result.

To be clear about what I meant by "sdk-specific helpers" before, I would still want to support something like this in the go sdk:

func main() {
    DaggerClient().Environment().
        WithCheck(IntegTest).
        Serve()
}

func IntegTest(ctx dagger.Context) (string, error) {
    // TODO: clientApp := Dagger().Democlient().Build()
    clientApp := DaggerClient().Apko().Wolfi([]string{"curl"})

    // TODO: need combined stdout/stderr really badly now
    return clientApp.
        WithServiceBinding("server", DaggerClient().Demoserver().Container()).
        WithExec([]string{"curl", "http://server:8081/hello"}).
        Stdout(ctx)
}

It's just that underneath the hood the go sdk will say "oh you gave me a func that returns a (string, error), I'll convert that to an EnvironmentCheckResult", with the conversion being basically what I explicitly wrote out in IntegTest before

#

Really what I'm thinking about right now is just the expectations in terms of the actual graphql api and what SDKs are expected to return when invoking a check

#

The actual way it gets presented to the end users in terms of the code they write is up to the SDK

fringe mountain
#

I understand all of that conceptually, my thinking was that I'm already doing this:

        try:
            result = await resolver.call(**inputs.args)
        except dagger.ExecError as e:
            rich.print(e.stdout)
            errors.print(e.stderr)
            sys.exit(e.exit_code)

So converting that to API calls or letting the API extract it, is the same thing.... in this context.

#

The problem right now is that the behavior gets really confusing once you start thinking about functions that are actually executing the check vs functions that are just assembling subchecks and calling them, etc. It's way simpler to just have the function return the check result.

Yeah. It makes sense.

timid seal
# fringe mountain I understand all of that conceptually, my thinking was that I'm already doing th...

Right so I think that would probably stay the same, the difference will be that when there isn't an error, an EnvironmentCheckResult should be returned.

Another thing this helps with is the whole problem of whether we should cache check failures or not. In this model, it may actually work out exactly how we want. If you run a check via WithExec and it fails, that won't be cached. But the execution of the check function itself won't return an error, so that means that it will return the same CheckResult until one of the inputs changes

#

As far as I can think that's actually exactly what we probably want?

#

Maybe? πŸ˜„

fringe mountain
#

Oh, that's neat! We can still have an unexpected exec failure due to a network issue for example, but we already have that ambiguity today though.

#

It's interesting thinking about the test as a unit a whole instead of caching only on the exec op or not. There's no way to do that today.

#

You can abstract a pipeline and control that cache, only lower level.

timid seal
fringe mountain
#

While here, you can do the error handling you need so the whole resolver is cached as a unit.

#

And it would be easy to pass another param/flag to invalidate that cache.

timid seal
#

Tons of interesting possibilities from that

timid seal
#

E.g. in theory for the Go env in universe, if there was a way to customize the environments container (which we should support someday), then you wouldn't need to pull the golang image, call WithExec on it, etc. You could literally just directly shell out to go build. There's a few other things needed to get that working seamlessly, but should be possible someday

fringe mountain
#

I remember Sam demoing CDK (or something) and one thing of note was that using those kinds of sdks doesn't take advantage of caching. So there's an argument there for using their CLIs instead so you can run in a container. Now it's a mute point. πŸ™‚

#

@timid seal, do we support any non-string arguments?

#

In commands, or checks.

timid seal
fringe mountain
#

I'd like optionals in the others as well πŸ™‚

timid seal
#

It's easy to support more types for the others too, the only part that requires some thinking/work is how those get passed when invoking via the CLI

fringe mountain
#

I think if it's optional and it's not set then don't send in input.args

timid seal
#

primitives like int, bool, etc. are obvious; support a list of primitives has a few options (i.e. repeated --list-arg foo --list-arg bar)

timid seal
fringe mountain
#

Let the user provide the default in the language.

timid seal
#

btw, I'm finishing up the refactor of how checks work we talked about above; it's a bit complicated because checks are now special in that the resolver provided by the user's environment code is not a resolver for the EnvironmentCheck object as a whole but instead a resolver for just the result: EnvironmentCheckResult! field. Which is great to support but is requiring some extra brain power to implement πŸ™‚ Think I'm pretty close now, should push soon

fringe mountain
#

What are you using as the return value type in Go (user func)?

timid seal
#

Once that's in place we can re-add the sugar for supporting other more convenient types, i.e. just an error or a list of existing checks that should just be called and combined into a result, etc. I actually think we probably can implement that sugar in the gql server-side rather than necessarily requiring SDKs to do it, but saving that for later atm

fringe mountain
#

I think I'll allow str | EnvironmentCheckresult at the start since it's very easy to do. Meaning that returning str would be the output, setting success=True, while an exception would make it success=False, with the exception's message as the output, and it still allows controlling with an EnvironmentCheckResult if needed.

timid seal
fringe mountain
#

Only yesterday I started thinking about functions. Didn't really realize what they were for (it was named something else) 🀯

#

Btw, I'm adding flags as you could tell. This works:

@env.command
async def publish(
    version: Annotated[str, "The version to publish."],
    image: Annotated[str, "The OCI image to use."],
) -> str:
    """Publish the client"""
    return await (
        dagger.container()
        .from_(image or "python:3.11.1-alpine")
        .with_exec(["echo", f"version: {version}"])
        .stdout()
    )
#

If you don't provide an image arg, it'll come up as empty string. When it support optionals it'll move to the param definition as image: str = "python:alpine".

timid seal
#

Pushed that! I also realized it was so simple to support a string being returned that I just went ahead and implemented it (like 3 extra lines of code), so it actually works with the _demo/client code already

#

The code for all this in the gql server is total spaghetti due to the whole thing of setting up a resolver for a field in Check rather than Check itself, but I can see how to make it nice again, just gonna be too much typing for the moment πŸ™‚

fringe mountain
#

I'm almost done with the arguments as well. So to sum up, what do I need to change? Just make sure checks return EnvironmentCheckResult or is there anything else?

timid seal
# fringe mountain I'm almost done with the arguments as well. So to sum up, what do I need to chan...

You might not need to change anything after all since I added the support for interpreting a string return as being the check output (and error being changed such that it's an "internal server error", which was already how python treated it). So you could add support also for returning an EnvironmentCheckResult, we will want that eventually for consistency, but I don't think it's strictly necessary anymore

fringe mountain
#

Yeah, but if I don't turn an exception into an EnvironmentCheckResult with success: false then it'll be an "internal server error", right?

timid seal
fringe mountain
#

@timid seal let me know what you need me to get working in Python today for next week as I'm going to be away and without my computer.

timid seal
# fringe mountain <@949034677610643507> let me know what you need me to get working in Python toda...

Yes will do! Started on artifacts last night, picking that back up now, should be done in the next 2 hours. The idea is that user code can return container, directory or file; in the case of python the def would just be annotated with @artifact I guess. For this first try, I'm not adding any sort of artifact-specific APIs; instead I'm just adding some APIs like WithLabel to directory and file so they all satisfy the artifact "interface" (though I'm not actually making use of gql interfaces yet)

fringe mountain
#

@timid seal, all the checks are still running, right? Can't have more than one.

timid seal
fringe mountain
#

Ok

#

How does python need to consume a non-universe function?

timid seal
#

In meeting for the next hour, will be off and on here

fringe mountain
#

Yeah, it's what I figured.

fringe mountain
#

@timid seal, I'm done for the day. If you can, I suggest you prioritize today having the python parts of the demo ready so that if something doesn't work right I can still fix it tomorrow before traveling. There's no support yet for anything sub-xxx btw.

timid seal
fringe mountain
#

I'll be able to add artifacts later tonight if it's ready.

timid seal
# fringe mountain I'll be able to add artifacts later tonight if it's ready.

Yeah it should be, I had to back out of my original idea to allow SDKs to return either a container, directory or file ID and then handle the conversion server-side; it causes too many weird cases around trying to determine what it is and other weird things in the api; so I'm just adding the ability in the go sdk to do that conversion itself. But that's all that's left, so shouldn't take much longer

fringe mountain
#
  • Functions, commands and checks are working.
  • Didn't try shell.
  • Arguments and outputs with dagger types are working.
  • Checks returning str are being converted into check result, which should support arbitrary nesting but I didn't test subresults.
  • Checks can also return check result type directly.
  • Each resolver type has a list of allowed types, but the conversion is general for any dagger type and primitives.
  • I tested a function returning a container and used in another env with another function receiving a container.
timid seal
timid seal
# fringe mountain I'll be able to add artifacts later tonight if it's ready.

Just pushed artifacts support; dagger artifact list lists them and dagger artifact export --output /some/path <artifact name> exports it to a local path (file and dir behave in obvious way, container is exported as a tar). Can add more subcommands as desired (dagger artifact publish would be good for container, for file/dir it could either publish an OCI artifact or just be an error for now, not sure yet)

On the SDK side, i went with what I mentioned above in that the user code can return a container, directory or file and the SDK needs to convert that to the right artifact api calls and return the resulting artifact ID. In the go sdk this required another nice chunk of kludge: https://github.com/sipsma/dagger/blob/183e1b22c23de3b36d3ec337bf6201e0ff81b5f8/sdk/go/serveArtifact.go#L118-L118

In general, I'm very glad we went through all these entrypoint types because it turns out pretty much all of them have their own special-little-requirements. That's leading all the code to just be full of kludge and spaghetti atm, but now when we go back and work on the mergeable PRs we can try to find the more general patterns and get a cleaner implementation... but in the meantime hopefully this doesn't throw too huge a wrench into the python SDK code πŸ˜”

But like I said, we'll be totally fine if the python SDK doesn't have artifacts yet for the demo, so don't try too hard if it's too big a wrench πŸ˜„

timid seal
fringe mountain
#

That's awesome πŸ˜ƒ

#

Hey, looking at the artifacts resultConverter and there's something I'm not getting.

#

Since there's two times this is run (with -schema and without it), the resolver is registered with one ID, but when executing it'll have another ID, but same name and description, right? Is there some match based on name on the server?

timid seal
#

there's zero chance that's how we'll end up doing this in the end, but technically worked for now

fluid coyote
#

@timid seal did you forget to push _demo/client/main.py changes by any chance? getting this, after codegen too:

universe/_demo/main.go:43:43: DaggerClient().Democlient().Build undefined (type *Democlient has no field or method Build)
timid seal
#

wtf I have no idea what I did, all the changes from client are missing... fixing

#

just pushed

#

I did a rebase and force push at some point and must have messed up somehow, though kind of confused what exactly

fluid coyote
#

thanks! πŸ™

fringe mountain
#

Added artifact support, now testing.

#

Having an issue with order or flags in CLI: Error: unknown shorthand flag: 'e' in -e, that's for the --env.

timid seal
#

pushed the fix

fringe mountain
#

Oh it works on first try! It's beautiful 🌈

#

This is so awesome.

timid seal
#

Awesome! I did just notice that dagger artifact list is giving me Field "export" argument "path" of type "String!" is required but not provided., which I'm pretty sure must have started after I added the export field to Artifact. The bug being that our go sdk requests all fields of an object when returning a list 😩

#

dagger artifact export --output <path> <artifact name> works still tho

fringe mountain
#

Also so cool how this can be extended without much work from the SDK. _artifacts.py is 37 lines long, mostly with boilerplate code.

timid seal
#

But yeah overall I think the environment builder api is gonna make lives much easier for SDK devs rather than having to construct graphql schemas and such

fringe mountain
#

_commands.py which is pretty standard is just 17 lines.

#

Oh, and we no longer have a strawberry dependency. There's more code than before for the same things, but it's not that much and it makes for a strong core where all the resource types are implemented around, with more capabilities.

#

In the client's build function there's no need for the awaits since all of that is lazy. You only need an await whenever you need ctx/err in go.

timid seal
timid seal
fringe mountain
#

πŸ™‚