#How do you implement pipeline steps that

1 messages Β· Page 1 of 1 (latest)

opal marten
ember glacier
#

What should I look at exactly?

I mean, I don't really see the solution. If the go test command fails, CoverageReport isn't going to return anything as far as I can tell.

Here is why:

dagger core container from --address alpine with-new-file --path '/foo' --contents 'bar' with-exec --args 'baz' file --path '/foo'
opal marten
ember glacier
#

I may be missing something, but I don't understand how that's possible after a WithExec fails with a non-zero exit code.

jolly nova
#

@ember glacier I'm on a call but I've implemented this pattern quite a lot, will share notes shortly

ember glacier
#

Thanks!

jolly nova
#

@ember glacier so, a few things:

  1. The core API does not handle this cleanly. It has a pet pieve of mine for a while, but anyway that's the situation at the moment.
  2. I do this in domain-specific modules. Some tools actually have a flag for this, which avoids hacks. Example with golangci-lint: https://github.com/dagger/dagger/blob/main/modules/golangci/lint.go#L115
  3. When I do need to resort to a hack, I run a shell script with || true. In practice it's fine. Doing it at the level of a domain-specific module makes it particularly benign. Example for a markdown linter: https://github.com/dagger/dagger/blob/main/modules/markdown/main.go#L58
  4. I would like to define a standard Checker interface or equivalent, and start using that for easier aggregation of many checks with less boilerplate. Ultimately this could become a dagger check convenience. Want to collaborate on that? πŸ™‚ I think a checker could be a test suite, lint run, code formatter, or anything else that outputs "green / red" + a report

As for markdown reports and reducing noise. May I suggest also using my Github Actions config which generates a markdown summary out of the box πŸ™‚ Example: https://github.com/cubzh/cubzh/actions/runs/10409324152#summary-28828642977

ember glacier
#

Thanks @jolly nova !

What I'm missing from the above is a way to communicate that the pipeline in fact failed (for example: there were lint violations). If I understand it correctly, the above approach leads to a green CI even when something breaks.

Want to collaborate on that?
I'd love to!

As for markdown reports and reducing noise. May I suggest also using my Github Actions config which generates a markdown summary out of the box

I'll take a look

#
    // +defaultPath="/"
    // +ignore=["*", "!core", "!deps/libz"]

Did I miss something?

jolly nova
#

(these comments won't work until context dir ships)

ember glacier
#

Ah, okay πŸ™‚

jolly nova
#

What I'm missing from the above is a way to communicate that the pipeline in fact failed (for example: there were lint violations). If I understand it correctly, the above approach leads to a green CI even when something breaks.

IMO with this pattern, you decouple the work of producing a test report, from the work of querying the report and returning an error (or not).

So initially, to preserve the same interface to CI, you would call a higher-level dagger function that queries the report and returns an error to CI like before.

But later, you might decide to update the PR status directly from the Dagger function, instead of letting the CI runner do it. For example Circle CI started pushing this pattern where you upload test reports to their test visualization service. Of course they do it for lock-in reason, but we can copy this pattern and apply it to portability πŸ™‚

Then of course there are use cases beyond CI, for example generating a test matrix grid, where the concept of "failed" or "passed" is irrelevant, you just generate a report for the user to browse separately.

TLDR: decouple decouple decouple

jolly nova
#

For example, here's how to query the golangci linter for details:

dagger call -m github.com/dagger/dagger/modules/golangci \
 lint --source https://github.com/dagger/dagger \
 issues \
 summary

You can export it as structured data for further processing outside dagger:

dagger call --json -m github.com/dagger/dagger/modules/golangci \
 lint --source https://github.com/dagger/dagger \
 issues \
 summary \
| jq .

Example output:

[
  {
    "summary": "[gosec] cmd/dagger/flags.go:495: G204: Subprocess launched with a potential tainted input or cmd arguments"
  },
  {
    "summary": "[exportloopref] cmd/dagger/cloud.go:98: exporting a pointer for the loop variable org"
  },
  {
    "summary": "[exportloopref] dagql/objects.go:132: exporting a pointer for the loop variable field"
  },
  {
    "summary": "[exportloopref] engine/buildkit/executor_spec.go:414: exporting a pointer for the loop variable mnt"
  }
]

As a convenience if you just want to decide whether to return an error, you can get the error count:

dagger call -m github.com/dagger/dagger/modules/golangci \
 lint --source https://github.com/dagger/dagger \
 error-count
twin vigil
# jolly nova For example, here's how to query the golangci linter for details: ```console da...

@jolly nova @grave rivet asked the same thing here today https://discord.com/channels/707636530424053791/1275080892410499163

What I think the above pattern is missing is the ability to get both, the status report and making the dagger CLI to error at the same time in the same dagger call

So if you're doing something like this today: golangci-lint ./foo 2> report.json and your lint fails, you get both the report and correct status code in the same step. IIUC, this is not possible with a single dagger call. You'd have return some struct with both the report and the error code, and use the --json flag to parse it from some wrapper command

ember glacier
#

IMO with this pattern, you decouple the work of producing a test report, from the work of querying the report and returning an error (or not).

Yeah, I understand that part. But I also think it's pretty unconventional and a huge shift from how people built CI pipelines.

But later, you might decide to update the PR status directly from the Dagger function, instead of letting the CI runner do it.

I experimented with that. It isn't necessarily a bad approach, but then you need to start thinking about retries to ensure results are correctly reported.

That's what actually keeps me from going down this path right now: I just wanted to generate a frickin test report and now I have to rethink how results are reported back.

jolly nova
#

eg. if you want to export it locally then have the CI script upload it somewhere - then move the upload phase into dagger. etc

ember glacier
jolly nova
ember glacier
jolly nova
ember glacier
#

run: echo '### Hello world! :rocket:' >> $GITHUB_STEP_SUMMARY

#

I would generate the report and export it as a file to the runner filesystem

jolly nova
ember glacier
#

yep

jolly nova
#

like in a markdown quote block perhaps?

ember glacier
#

No, the exact same way your function generates the summary πŸ˜„

jolly nova
#

ah I see.

ember glacier
#

BUT, I'd still want the ci job to result in a failure

jolly nova
#

we do have a blind spot there

ember glacier
#

I want to use the test and lint reports generated inside my ci function. In fact I want to generate the report.md file from inside a function.

#

And ideall, I would want Dagger to signal which step caused the failure. Simply returning exit 1 after the function exits wouldn't do that.

jolly nova
#

maybe we could have a standard interface for CI integrations. If you return a type that implements that interface, CI integrations are expected to use it to get the information they need (eg. job status)

#

in fact maybe that interface is the Check interface I mentioned earlier in the thread

ember glacier
#

Well, I might be oversimplifying things, but in my mind, a similar construct as "continue on error" would be useful. In Dagger terms: WithExec().File() could still return the file if there is one. Dunno if that's feasible.

jolly nova
#

Yeah but that's not the real blocker (you can use the tricks I described before). It's cosmetic. The real blocker is that you can return a string, or file, back to the CLI caller

#

And no it's not feasible, at least not easily. I think it's tightly coupled to how the engine implements the GraphQL spec. You make a query, if it fails, you get an error.

ember glacier
#

Well, if pointing to the right step in the pipeline as the cause of failure is cosmetics, then I guess it is.

jolly nova
#

No what's cosmetic is how you do it. You can already achieve that by wrapping in a shell script, and if needed, getting the exit code out to decide to throw an error

ember glacier
#

I'm gonna give those workarounds a try, but it's gonna take some getting used to. It's quite...unusual.

jolly nova
#

My point was that there is a more pressing problem to solve than that WithExec. It's your problem of getting the report out to CI and also reporting the error. The workaround won't help with that. We need to find a solution to that together.

ember glacier
#

Yep

twin vigil
jolly nova
wooden brook
#

Thanks for the support and replies @dark yoke @twin vigil @jolly nova

Its very common in the PHP ecosystem to get the PHP coverage report, even if the suite fails or not, and we can't really ask people to migrate over to Dagger until this is possible.

Coverage reports are part of the Unit testing suite, which is typically the 2nd thing ran

  1. Linting + Static Analysis
  2. Unit
  3. Functional/Integration
  4. Acceptance/API/UI

In the PHP SDK we're able to run the static analysis, no problem, so it's just Unit related features that need to be here (prioritized hopefully?) so we can progress in the "Readiness of Dagger" that the PHP community (and I'm sure wider communities) will be evaluating.

last talon
# ember glacier I may be missing something, but I don't understand how that's possible after a W...

I'm late to this thread but I think it's still valuable to talk about the workaround I've tried. It's similar to the workarounds noted above but not exactly the same I think. In the function that Lev linked, it turned out to not need the report if the tests failed, so I ended up removing the convenience functions at some point.

The way my workaround is a bit different is that it just used 2 functions to accomplish the report + error rather than the various ways above to try to get this in one function.

It looked something like

dagger call test ... report -o ./report.xml # <-- this gets the report no matter what the test status is

# then, call a different function on the test interface

dagger call test ... success # <-- this just does a sync on the test command, so the exit code will reflect the test results and avoids parsing json
jolly nova
#

@wide notch re: alwaysExec. Sometimes the test tool has a flag to not return an error on failed tests, that makes the shell wrapper unnecessary. So in my experience it's a case-by-case thing

jolly nova
#

Also the alwaysExec won't solve the problem of the entrypoint function. The workaround we're discussing is very similar to your custom TestResult type idea. I've been calling it Check because it can also include linters and other green/red use cases beyond tests. But same general idea.

@wooden brook is correct that a custom type is specific to just you... Unless we make it an interface, then work together to define a convention. Then we can all implement the same interface, both on the callee side (dagger functions that return that type) and the caller side (CI integrations that know what to do with it).

wide notch
wooden brook
#

πŸ™ƒ

#

What do you think of my name suggestion ?

wide notch
#

we can come back to that πŸ™‚

#

perhaps we need to be able to differentiate between withExec -> that is the equivilent of doing a dockerfile run and is not expected to fail and a runThisThing which is part of the validation of the build which may fail and you may want to handle that in your own way but still have the step as a whole marked as a failure

dark yoke
#

starting to think about technical implementations of doing this in the core api (no promises, just trying to scope right now)
generally, for php tooling is the list of "valid" exit codes for a tool known? e.g. is it something like 0 on success, 1 on fail? or is it more like 0 on success, entirely random exit code on fail?

#

my reasoning is - i could see a field that was like "ValidExitCodes" that's a list, and you could pass [0, 1] to it. i'm not sure about a generic "allow all exit codes here"

#

additionally, all exit codes < 0 should probably always fail regardless right? since those mean the process was terminated by a signal (like SIGKILL/SIGILL/SIGSEGV)

wide notch
#

I think it's reasonable to assume that the exit codes are knowable; a valid exit codes would be only a half fix as you'd still want to be able to get the exit code to know if the step completed or not

#

I think some sort of Check interface is going to be the way to go

#

though that relies on saving the execution details inside the container for retrevial - ideally something in core could store them externally for retrevial as part of a dagger object

opal marten
wooden brook
#

@opal marten @last talon @jolly nova

Hey πŸ‘‹ I'm picking up this topic again.

Yesterday I watched the dagger community call, and the speaker (2nd person?) Was exeutung typescript tedt suite.. with failing tests ..

  1. He was showing the code coverage report .. I'm wondering how he managed to get the coverage report outside of the running dagger container, if it failed?

  2. Where are we at, in general, with a permanent fix for what we are discussing in this discord thread?

Thanks πŸ‘

last talon
wooden brook
#

Hey @last talon

Yep. I'm aware of this technique. We diacussiled it above, by trapping ?$
I'm wondering if we came up with an actual solution to it rather than this workaround thing.

Looks like we haven't yet.

I'm wondering what's the priority of this, on our dagger roadmap and if we have an ETA on a solution to this.

For example in Jenkins we can do

code = sh(cmd, return_code=true)

publishReport()

return code;

wooden brook
#

@last talon we need a user friendly dagger handler for this.

The majority of CI test runs will be false. And the final one(s) will be green.

So making this "just work" is really important to DX, and the adoption of dagger.

A custom object like @wide notch mentioned or similar can be fine for people using that instead of the withExec() because we know we are calling a test suite that can fail, and that's okay, whilst we do cleanups and report publishing.

What are your thoughts ?

opal marten
#

Yeah I think we either need a new primitive or update the existing with_exec to handle this somehow. This came up in conversation this morning with someone else I was talking to live.

jolly nova
#

Guys like I said before, with_exec is not actually the biggest blocker - it's an inconvenience sure, but there is also a bigger blocker, which is the dagger call behavior which will either return an error or a result to the CLI caller, but not both

#

and we don't have a fix in flight for either of these things

#

cc @silk pine @sterile swallow to put this on your radar

wide notch
dark yoke
silk pine
dark yoke
#

πŸ€” yes

#

i think that makes sense?

#

e.g. if you're doing a lint report, and it could return 0 or 1, then you want it to cache if the files haven't changed

#

is there a case you wouldn't want it to cache?

#

imo, i do think we should be able to mark individual steps as no-cache as a future extension, but in the meantime, i think if the exit code is in the specified list, we should treat the step as "succeeded", and cache it

#

(as an aside, i have no idea how to determine whether to cache a step after it's executed - that's gonna be some insane buildkit scheduling work i think)

flint pendant
silk pine
dark yoke
#

i do think that the "right" way to solve this would be by using a cache buster though - if you expect flakes, then you should never cache the results, even successes

#

just caching the successes and not the failures gives you a misguided view of how stable your test suite is (no, of course i've never been guilty of this :P)

silk pine
#

that's a noble POV but I think people would be angry with in practice πŸ˜›

#

ie "i know it's not perfect but i don't wanna be punished until I can fix it 😭"

#

but, i'm not arguing against the behavior, i think it makes sense for there to be a mechanic where you can cache a failure (linting is a great example) (and ldd for checking static builds)

dark yoke
#

mm, i get that, i just think it's a bit weird to explain (in future docs) why some steps might "succeed" (and get their little tick in dagger cloud, etc), but don't end up cached

silk pine
#

succeed because exit-status==2 and 2 was one of the expected exits?

dark yoke
#

mm

#

yeah

#

out of curiosity - do you think this is a blocker for the feature?

silk pine
#

i think i'd still expect a red tick for that tbh. but i might be missing context from earlier in the thread

dark yoke
#

if we have to not cache fails, i'm not sure how we actually do that

silk pine
dark yoke
#

😒 fair enough

#

πŸ˜„ let the bikeshedding begin πŸŽ‰

silk pine
#

just my 2c! if it makes sense to everyone else i'm not blocking πŸ˜›

dark yoke
#

but i don't think it's a red cross

#

like it's this weird new third state

#

"this failed, but we continued onwards anyways"

#

although... i'm not sure i neccessarily know if I agree with the idea that it failed.
i could imagine writing e2e tests in dagger, and running commands that I expect to fail - e.g. our own integration tests. technically, when the dagger call "fails", it's doing the right behavior, it's a success (cloud's visualization of this atm is kinda odd, because we see a bunch of red spans, but we expect those spans to be red, but there's no way of indicating that)

#

i guess what i'm saying is i don't think "non-zero exit code == failure" and "zero exit code == success"

silk pine
#

yep - right now that's handled by the parent span. if the child span failed, and that was not expected, the parent span interprets that and bubbles up the failure. if it was expected, the parent span succeeds, and the failed span will still be failed, but it won't be bubbled up, since the parent span covers for it

#

(brb doctor)

dark yoke
flint pendant
flint pendant
# dark yoke like it's this weird new third state

In relation to https://github.com/dagger/dagger/issues/8141

Currently I have no qualm with non-zero exit codes still being considered a failure. My main issue is that I cannot make use of that failure, I cannot dig into it at all. Not without using workarounds like this

GitHub

Real Example of Current Behaviour When my source directory has formatting errors, if I call: dagger/sdk/php $ dagger call -m dev format --source=. export --path=.: The format command begins: The fo...

GitHub

Execute commands in a Dagger Container without causing an error, regardless of the exit code. - charjr/dagger-always-exec

#

Speaking literally of my use-case (and I welcome anyone to throw in a contradictory use-case) I am happy for it to still be a failure, but I need to be able to look at that failure.

For instance, if I use PHPCBF (PHP Code Beautifier and Formatter) exit code 1 is given when code needed formatting and it successfully formatted it. I need to be able to continue from that exit code and export the code it formatted.

wide notch
#

I think the key thing, which perhaps is another feature is to be able to retrieve the exit code from the last withExec() call.

silk pine
#

in Python/TS there's probably an exception with the same info

dark yoke
#

mm, if we make it stop throwing an error for specified exit codes though, we need another way of grabbing this πŸ€”

silk pine
#

yeah - we used to have an exitCode API but it was removed when we realized its only possible return value was 0 πŸ˜‚

#

which your feature would fix!

wide notch
#

yeah, the PHP SDK also has the same exception handling, however this PR means there won't be an exception/error so we won't get the code :p

dark yoke
#

i'll make sure to add exitCode to container in that pr - i've added a note πŸ˜†

twin vigil
dark yoke
twin vigil
wooden brook
#

Good evening daggernauts 🫑 happy choos day πŸš† πŸš‰

twin vigil
#

@silk pine I was reading about your take on the type Error API change. I really like the idea and I think it'll allow us to be way more expressive while setting and handling errors. I have two questions / observations about the current API.

1- Should error have a JSON type similarly to returnValue so we can eventually assign dynamic types to that?
2- How does this unblock us for the OP's use-case in this thread? Since now functions can return richer error type but from the CLI perspective we'll still have to deal with making the dagger call fail with some status code, and still be able to return something from it.

#

One thing that we were speaking with @jolly nova yesterday was the fact that it's not mandatory for us to necessarily fail with a status code != 0. In a similar way which GraphQL didn't comply with the HTTP / REST standard, we think it's ok to be a bit more disruptive here while giving the flexibility to the user to express what they need

silk pine
# twin vigil <@108011715077091328> I was reading about your take on the `type Error` API chan...

1 - Wasn't planning on that, because at that point we would need to keep track of two return types, which starts to feel a little complicated, especially if there are multiple types of errors an API author may want to return. We can get away with the JSON representation for returnValue because we know what type the function returns. For my proposal there's currently just a single blessed Error type which we could possibly extend to support error hierarchies and attaching arbitrary data, so my main question is whether that's enough to satisfy this 'error reports' use case.
2 - Hmm, well it wasn't really intended to solve this specific issue - my goal was just to have answer at all for how functions can return errors, since that's currently missing completely leaving us with awful exec /runtime: exit status 2 errors. But, provided there's some way to get structured data out of e.g. a failed go test (which might require Justin's proposal to support chaining from failed commands), you would be able to take that and turn it into an Error hierarchy. At that point it wouldn't be "I successfully returned a test report", it'd be "I failed, and here's the failure report"

So yeah, the big question is whether that's enough to cover all these use cases (lint + test, ???). Personally, I think it'd be an uphill battle to "successfully return a failed report" - I think you'd still want a nonzero exit status from call for example. Seems like anything else would be playing wack-a-mole and manually adding early exits, or mistakenly getting green Actions runs that emit failed reports, etc.

#

The other advantage of having a single Error type is it seems like it'd be much easier to work with in the UI and in code, as opposed to arbitrary error types or report types

#

The trade-off would be that if we support adding arbitrary data to it, you'll be duck-typing when you pull it back out

twin vigil
twin vigil
silk pine
#

well, if a module were able to return its own custom types (like test/lint reports or its own custom error implementations) it'd be strongly typed the whole time

#

maybe we could have an Error interface?

#

not sure if it's worth it πŸ€·β€β™‚οΈ

twin vigil
silk pine
#

sorry, meant custom error types there

twin vigil
#

non "error as values" languages can catch the concrete type I assume, but that's a from of type casting basically...

#

:typescript: 🀣

silk pine
#

yeah I think in Go we'd make sure *Error implements error, and do the analogous thing for exception-based languages. So in Go you'd attempt a cast to *Error and start inspecting from there. With interfaces, I'm not quite sure how it'd work - you might have to cast it to *Error and then try .AsFooError() I guess? Feels like a lot of extra hops. (Also, don't even want to think about the case where you get a *Error but then msg, err2 := err.Message(ctx) fails πŸ’€)

#

tbh I hate the pattern of catching/handling various error types

#

it's like a janky API within an API

#

so yeah as you can see we've quickly exceeded the scope of my bikeshed/proposal so far πŸ˜›

#

the MVP for me is just improving on the exec /runtime: exit status 2 errors

silk pine
#

maybe that's where GraphQL error responses come in ... somehow

twin vigil
flint pendant
# twin vigil <@1229425915675807797> <@488718750690967563> thinking about this in the context ...

The initial cookbook workaround is part of what we want, this is my current workaround.

Basically, on a non-zero exit code, we need:

  1. A way to view the [result](#1275216033862647890 message) of the last withExec
  2. A way to continue using the container if the result is acceptable.

#8466 solves 2. But it does not solve 1.

GitHub

Fixes #5981, #8141.
Warning
Depends on moby/buildkit#5339 for the logic that actually makes this work.

dark yoke
#

upstream has merged, so i'm gonna get this one ready to go today - i'm gonna do both parts ❀️

twin vigil
# flint pendant The initial cookbook workaround is part of what we want, this is [my current wor...

IIUC with #8466 you could also achieve 1, correct?

I'm imagining something like this:

func (m *Lala) Test() {

    ctx := context.Background()
    c := dag.Container().From("alpine").
        WithExec([]string{"sh", "-c", "touch foo.txt && exit 1"}, dagger.ContainerWithExecOpts{ExitCodes: []int{1}})

    ec, err := c.ExitCode(ctx)

    if err != nil {
        return nil, err
    }
    // do whatever with the exit code and continue the pipeline here.

    c.WithExec([]string{"do", "something", "else"})

}
ember glacier
#

Does that mean if a WIthExec fails with a non-zero exit code and the particular step was supposed to generate a file, it's going to be there?

flint pendant
flint pendant
# twin vigil IIUC with #8466 you could also achieve 1, correct? I'm imagining something li...

The dagger internals are a a bit of an unknown to me, so I read the tests to understand what behaviour is implemented.

What I don't see in the tests, is if I still get stdout and stderr. Would these still print out as normal?

It's not a blocker for the PR, @dark yoke has done amazing work and I'm excited to see it go in! Being able to get the exit code does fix issue #8141.

GitHub

An engine to run your pipelines in containers. Contribute to dagger/dagger development by creating an account on GitHub.

GitHub

Fixes #5981, #8141.
Pulls in a buildkit update as well, so we can get moby/buildkit#5339

dark yoke