Skip to content

Commit

Permalink
Switch from Progrock to OpenTelemetry (#6835)
Browse files Browse the repository at this point in the history
* progrock -> otel

* All Progrock plumbing is now gone, though we may want to bring it back
  for compatibility. Removing it was a useful exercise to find the many
  places where we're relying on it.
* TUI now supports -v, -vv, -vvv (configurable verbosity). Global flags
  like --debug, -v, --silent, etc. are processed anywhere in the command
  string and respected.
* CLI forwards engine traces and logs to configured exporters, no need
  to configure engine-side (we already need this flow for the TUI)
* "Live" spans are emitted to TUI and cloud, filtered out before sending
  to a traditional (non-Live) otel stack
* Engine supports pub/sub for traces and logs, can be exposed in the
  future as a GraphQL subscription
* Refactor context.Background usage to context.WithoutCancel. We usually
  don't want a total reset, since that drops the span context and any
  other telemetry related things (loggers etc). Go 1.21 added
  context.WithoutCancel which is more precise.
* engine: don't include source in slogs. Added this prospectively and it
  doesn't seem worth the noise.
* idtui: DB can record multiple traces, polish
  * multi traces is mostly for dagviz, so i can run it with a single DB
  * add 'passthrough' UI flag which tells the UI to ignore a span and
    descend into its children
  * add 'ignore' UI flag, to be used sparingly for things whose
    signal:noise ratio is irredeemibly low (e.g. 'id' calls)
  * make loadFooFromID calls passthrough
  * make Buildkit gRPC calls passthrough
* Global Progrock rogs are theoretically replaced with
  tracing.GlobalLogger, but it has yet to be integrated into anything.
* Module functions are pure after all. They're already cached
  per-session, so this makes DagQL reflect that, avoiding duplicate
  Buildkit work that would be deduped at the Buildkit layer. Cleans up
  the telemetry since previously you'd see duplicate queries.

* TODO: ensure draining is airtight
* TODO: global logging to TUI
* TODO: batch forwarded engine spans instead of emitting them "live"
* TODO: fix dagger terminal

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix log draining, again, ish

previously we would cancel all subscribers for a trace whenever a
client/derver went away. but for modules/nesting this meant the inner
call would cancel the whole trace early.

* TODO: looks like services still don't drain completely?

Signed-off-by: Alex Suraci <alex@dagger.io>

* don't set up logs if not configured

Signed-off-by: Alex Suraci <alex@dagger.io>

* respect configured level

Signed-off-by: Alex Suraci <alex@dagger.io>

* clean up shim early tracing remnants

Signed-off-by: Alex Suraci <alex@dagger.io>

* synchronously detach services on main client exit

previously service spans would be left incomplete on exit. now we'll
detach from them on shutdown, which will only stop the service if we're
the last depender on it. end result _should_ be that services are always
completed through telemetry, but I've seen maybe 2 in 50 runs still
leave it running. still troubleshooting, but without this change there
is no hope at all.

fixes #6493

Signed-off-by: Alex Suraci <alex@dagger.io>

* flush telemetry before closing server clients

Honestly not 100% confirmed, but seems right. I think the final solution
might be to get traces/logs out without going through a session in the
first place.

Signed-off-by: Alex Suraci <alex@dagger.io>

* switch from errgroup to conc for panic handling

seeing a panic in ExportSpans/UploadTraces, this should help avoid
bringing whole server down - I think - or at least give us hope.

Signed-off-by: Alex Suraci <alex@dagger.io>

* nest 'starting session' beneath 'connect'

Signed-off-by: Alex Suraci <alex@dagger.io>

* send logs out from engine to log exporter too

Signed-off-by: Alex Suraci <alex@dagger.io>

* bump midterm

Signed-off-by: Alex Suraci <alex@dagger.io>

* switch to server-side telemetry pub/sub

fetching the logs/traces over a session is really annoying with draining
because the session itself gets closed before things can be fully
flushed.

Signed-off-by: Alex Suraci <alex@dagger.io>

* show newer traces first

Signed-off-by: Alex Suraci <alex@dagger.io>

* cleanup

Signed-off-by: Alex Suraci <alex@dagger.io>

* send individual Calls over telemetry instead of IDs

More than a 10x efficiency increase. Frontend still super easy to
implement.

Test:

  # in ~/src/bass
  $ with-dev dagger call -m ./ --src https://github.com/vito/bass unit --packages ./pkg/cli stdout --debug &> out
  $ rg measuring out | cut -d= -f2 | xargs | tr ' ' '+' | sed -e 's/0m//g' -e 's/[^0-9\+]//g' | cat -v | bc

Before:

  8524838 (~8.1 MiB)

After:

  727039 (~0.7 MiB)

Signed-off-by: Alex Suraci <alex@dagger.io>

* idtui Base was correct in returning bool

Signed-off-by: Alex Suraci <alex@dagger.io>

* handle case where calls haven't been seen yet

kinda hacky, but it makes sense that we need to handle this, cause
loadFooFromID or generally anything can take an ID that's never been
seen by the server before, and the loadFooFromID span will come first.

Signed-off-by: Alex Suraci <alex@dagger.io>

* idtui: add space between progress and primary output

Signed-off-by: Alex Suraci <alex@dagger.io>

* swap -vvv and -vv, -vv now breaks encapsulation

Signed-off-by: Alex Suraci <alex@dagger.io>

* cleanups

Signed-off-by: Alex Suraci <alex@dagger.io>

* tidy mage

Signed-off-by: Alex Suraci <alex@dagger.io>

* tidy

Signed-off-by: Alex Suraci <alex@dagger.io>

* loosen go.mod constraints

Signed-off-by: Alex Suraci <alex@dagger.io>

* revive labels tests

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix cachemap tests

Signed-off-by: Alex Suraci <alex@dagger.io>

* nuclear option: wait for all spans to complete

Rather than closing the telemetry connection and hoping the timing works
out, we keep track of which traces have active spans and wait for that
count to reach 0.

A bit more complicated but not seeing a simpler solution really. Without
this we can't ensure that the client sees the very outermost spans
complete.

Signed-off-by: Alex Suraci <alex@dagger.io>

* pass-through all gRPC stuff

hasn't really been useful, it's available in the full trace for devs, or
we can add a verbosity level.

Signed-off-by: Alex Suraci <alex@dagger.io>

* dagviz: tweaks to support visualizing a live trace

Signed-off-by: Alex Suraci <alex@dagger.io>

* better 'docker tag' parsing

Signed-off-by: Alex Suraci <alex@dagger.io>

* fixup docker tag check

Signed-off-by: Alex Suraci <alex@dagger.io>

* pass auth headers to OTLP logs too

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix stdio not making it out of gateway containers

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix terminal support

Signed-off-by: Alex Suraci <alex@dagger.io>

* drain immediately when interrupted

otherwise we can get stuck waiting for child spans of a nested process
that got kill -9'd. not perfect but better than hanging on Ctrl+C which
is already an emergent situation where you're not likely that interested
in any remaining data if you already had a reason to interrupt.

in Cloud we'll clean up any orphaned spans based on keepalives anyway.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix unintentionally HTTP-ifying gRPC otlp enpoint

Signed-off-by: Alex Suraci <alex@dagger.io>

* give up retrying connection if outer ctx canceled

Signed-off-by: Alex Suraci <alex@dagger.io>

* initiate draining only when main client goes away

Signed-off-by: Alex Suraci <alex@dagger.io>

* appease linter

Signed-off-by: Alex Suraci <alex@dagger.io>

* remove unnecessary wait

we don't need to try synchronizing here now that we just generically
wait for all spans to complete

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix panic if no telemetry

Signed-off-by: Alex Suraci <alex@dagger.io>

* remove debug log

Signed-off-by: Alex Suraci <alex@dagger.io>

* print final progress tree in plain mode

no substitute for live console streaming, but easier to implement for
now, and probably easier to read in CI. probably needs more work, but
might get some tests passing.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix Windows build

Signed-off-by: Alex Suraci <alex@dagger.io>

* propagate spans through dagger-in-dagger

Signed-off-by: Alex Suraci <alex@dagger.io>

* retry connecting to telemetry

Signed-off-by: Alex Suraci <alex@dagger.io>

* propagate span context through dagger run

Signed-off-by: Alex Suraci <alex@dagger.io>

* install default labels as otel resource attrs

Signed-off-by: Alex Suraci <alex@dagger.io>

* tidy

Signed-off-by: Alex Suraci <alex@dagger.io>

* remove pipeline tests

these are expected to fail now

Signed-off-by: Alex Suraci <alex@dagger.io>

* fail root span when command fails

Signed-off-by: Alex Suraci <alex@dagger.io>

* Container.import: add span for streaming image

Signed-off-by: Alex Suraci <alex@dagger.io>

* idtui: break encapsulation in case of errors

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix schema-level logging not exporting

caught by TestDaggerUp/random

Signed-off-by: Alex Suraci <alex@dagger.io>

* update TestDaggerRun assertion

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix test not syncing on progress completion

Signed-off-by: Alex Suraci <alex@dagger.io>

* add verbose debug log

Signed-off-by: Alex Suraci <alex@dagger.io>

* respect $DAGGER_CLOUD_URL and $DAGGER_CLOUD_TOKEN

promoting these from _EXPERIMENTAL along the way, which has already been
done for _TOKEN, don't really see a strong reason to keep the
_EXPERIMENTAL prefix, but low conviction

Signed-off-by: Alex Suraci <alex@dagger.io>

* port 'processor: support span keepalive'

originally aluzzardi/otel-in-flight@2fc011f

Signed-off-by: Alex Suraci <alex@dagger.io>

* add 'watch' command

really helps with troubleshooting hanging tests!

Signed-off-by: Alex Suraci <alex@dagger.io>

* set a reasonable window size in plain mode

otherwise the terminals resize a ton of times when a long string is
printed, absolutely tanking performance. would be nice if that were
fast, but no time for that now.

Signed-off-by: Alex Suraci <alex@dagger.io>

* manually revert container.import change

i thought this wouldn't break it, but ... ?

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix race

Signed-off-by: Alex Suraci <alex@dagger.io>

* mark watch command experimental

Signed-off-by: Alex Suraci <alex@dagger.io>

* fixup lock, more logging

Signed-off-by: Alex Suraci <alex@dagger.io>

* tidy

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix data race in tests

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix java SDK hang once again

really not sure what's writing to stderr even with --silent but this is
just too brittle. redirect stderr to /dev/null instead.

Signed-off-by: Alex Suraci <alex@dagger.io>

* retire dagger.io/ui.primary, use root span instead

fixes Views test; frontend must have been getting confused because there
were multiple "primary" spans

Signed-off-by: Alex Suraci <alex@dagger.io>

* take 2: just manually mark the 'primary' span

Signed-off-by: Alex Suraci <alex@dagger.io>

* merge tracing and telemetry packages

Signed-off-by: Alex Suraci <alex@dagger.io>

* cleanups

Signed-off-by: Alex Suraci <alex@dagger.io>

* roll back sync detach change

this was no longer needed with the change to wait for spans to finish,
not worth the review-time distraction

Signed-off-by: Alex Suraci <alex@dagger.io>

* cleanups

Signed-off-by: Alex Suraci <alex@dagger.io>

* update comment

Signed-off-by: Alex Suraci <alex@dagger.io>

* remove dead code

Signed-off-by: Alex Suraci <alex@dagger.io>

* default primary span to root span

Signed-off-by: Alex Suraci <alex@dagger.io>

* remove unused module arg

Signed-off-by: Alex Suraci <alex@dagger.io>

* send engine traces/logs to cloud

Signed-off-by: Alex Suraci <alex@dagger.io>

* implement sub metrics pub/sub

Some clients presume this service is supported by the OTLP endpoint. So
we can just have a stub implementation for now.

Signed-off-by: Alex Suraci <alex@dagger.io>

* sdk/go runtime: implement otel propagation

TODO: set up otel for you

Signed-off-by: Alex Suraci <alex@dagger.io>

* tidy

Signed-off-by: Alex Suraci <alex@dagger.io>

* add scary comment

Signed-off-by: Alex Suraci <alex@dagger.io>

* batch events that are sent from the engine

Previously we were just sending each individual update to the configured
exporters, which was very expensive and would even slow down the TUI.

When I originally tried to send it to span processors, nothing would be
sent out; turns out that was because the transform.Spans call we were
using didn't set the `Sampled` trace flag.

Now we forward engine traces and logs to all configured processors,
so their individual batching settings should be respected.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix spans being deduped within single batch

* fix detection for in flight spans; we need to check EndTime <
  StartTime since sometimes we end up with a 1754 timestamp
* when a span is already present in a batch, update it in-place rather
  than dropping it on the floor

Signed-off-by: Alex Suraci <alex@dagger.io>

* Add Python support

Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com>

* shim: proxy otel to 127.0.0.1:0

more universally compatible than unix://

Signed-off-by: Alex Suraci <alex@dagger.io>

* remove unnecesssary fn

Signed-off-by: Alex Suraci <alex@dagger.io>

* attributes: add passthrough, bikeshed + document

also start cleaning up "tasks" cruft nonsense, these can just be plain
old attributes on a single span i think

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix janky flag parsing

parse global flags in two passes, ensuring the same flags are installed
in both cases, and capturing the values before installing them into the
real flag set, since that clobbers the values

Signed-off-by: Alex Suraci <alex@dagger.io>

* discard Buildkit progress

...just in case it gets buffered in memory forever otherwise

Signed-off-by: Alex Suraci <alex@dagger.io>

* sdk/go: somewhat gross support for opentelemetry

had to copy-paste a lot of the telemetry code into sdk/go/. would love
to just move everything there so it can be shared between the shim, the
Go runtime, and the engine, however it is currently a huge PITA to share
code between all three, because of the way codegen works. saving that
for another day. maybe tomorrow.

Signed-off-by: Alex Suraci <alex@dagger.io>

* send logs to function call span, not exec /runtime

Signed-off-by: Alex Suraci <alex@dagger.io>

* tui: respect dagger.io/ui.mask

no more exec /runtime!

Signed-off-by: Alex Suraci <alex@dagger.io>

* silence linter

worth refactoring, but not now™

Signed-off-by: Alex Suraci <alex@dagger.io>

* ignore --help when parsing global flags

Signed-off-by: Alex Suraci <alex@dagger.io>

* Pin python requirements

Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com>

* revert Python SDK changes for now

looks like there's more to figure out with module dependencies? either
way, don't want this to block the current PR, they can be re-introduced
in another PR like the other SDKs

Revert "Pin python requirements"

This reverts commit b40c411.

Revert "Add Python support"

This reverts commit 08aa92c.

Signed-off-by: Alex Suraci <alex@dagger.io>

* fix race conditions in python SDK runtime

Signed-off-by: Alex Suraci <alex@dagger.io>

---------

Signed-off-by: Alex Suraci <alex@dagger.io>
Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com>
Co-authored-by: Helder Correia <174525+helderco@users.noreply.github.com>
  • Loading branch information
vito and helderco committed Apr 3, 2024
1 parent 64e6ab0 commit 6467198
Show file tree
Hide file tree
Showing 182 changed files with 10,546 additions and 8,718 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,3 @@ go.work.sum

# merged from dagger/examples repository
**/node_modules
**/env
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "telemetry/opentelemetry-proto"]
path = telemetry/opentelemetry-proto
url = https://github.com/open-telemetry/opentelemetry-proto
54 changes: 18 additions & 36 deletions analytics/analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"sync"
"time"

"github.com/dagger/dagger/core/pipeline"
"github.com/dagger/dagger/engine"
"github.com/vito/progrock"
"github.com/dagger/dagger/telemetry"
)

const (
Expand Down Expand Up @@ -78,30 +78,20 @@ func DoNotTrack() bool {

type Config struct {
DoNotTrack bool
Labels pipeline.Labels
Labels telemetry.Labels
CloudToken string
}

func DefaultConfig() Config {
func DefaultConfig(labels telemetry.Labels) Config {
cfg := Config{
DoNotTrack: DoNotTrack(),
CloudToken: os.Getenv("DAGGER_CLOUD_TOKEN"),
Labels: labels,
}
// Backward compatibility with the old environment variable.
if cfg.CloudToken == "" {
cfg.CloudToken = os.Getenv("_EXPERIMENTAL_DAGGER_CLOUD_TOKEN")
}

workdir, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get cwd: %v\n", err)
return cfg
}

cfg.Labels.AppendCILabel()
cfg.Labels = append(cfg.Labels, pipeline.LoadVCSLabels(workdir)...)
cfg.Labels = append(cfg.Labels, pipeline.LoadClientLabels(engine.Version)...)

return cfg
}

Expand All @@ -111,8 +101,7 @@ type queuedEvent struct {
}

type CloudTracker struct {
cfg Config
labels map[string]string
cfg Config

closed bool
mu sync.Mutex
Expand All @@ -128,15 +117,10 @@ func New(cfg Config) Tracker {

t := &CloudTracker{
cfg: cfg,
labels: make(map[string]string),
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
}

for _, l := range cfg.Labels {
t.labels[l.Name] = l.Value
}

go t.start()

return t
Expand All @@ -155,19 +139,19 @@ func (t *CloudTracker) Capture(ctx context.Context, event string, properties map
Type: event,
Properties: properties,

DeviceID: t.labels["dagger.io/client.machine_id"],
DeviceID: t.cfg.Labels["dagger.io/client.machine_id"],

ClientVersion: t.labels["dagger.io/client.version"],
ClientOS: t.labels["dagger.io/client.os"],
ClientArch: t.labels["dagger.io/client.arch"],
ClientVersion: t.cfg.Labels["dagger.io/client.version"],
ClientOS: t.cfg.Labels["dagger.io/client.os"],
ClientArch: t.cfg.Labels["dagger.io/client.arch"],

CI: t.labels["dagger.io/ci"] == "true",
CIVendor: t.labels["dagger.io/ci.vendor"],
CI: t.cfg.Labels["dagger.io/ci"] == "true",
CIVendor: t.cfg.Labels["dagger.io/ci.vendor"],
}
if remote := t.labels["dagger.io/git.remote"]; remote != "" {
if remote := t.cfg.Labels["dagger.io/git.remote"]; remote != "" {
ev.GitRemoteEncoded = fmt.Sprintf("%x", base64.StdEncoding.EncodeToString([]byte(remote)))
}
if author := t.labels["dagger.io/git.author.email"]; author != "" {
if author := t.cfg.Labels["dagger.io/git.author.email"]; author != "" {
ev.GitAuthorHashed = fmt.Sprintf("%x", sha256.Sum256([]byte(author)))
}
if clientMetadata, err := engine.ClientMetadataFromContext(ctx); err == nil {
Expand Down Expand Up @@ -203,34 +187,32 @@ func (t *CloudTracker) send() {
}

// grab the progrock recorder from the last event in the queue
rec := progrock.FromContext(queue[len(queue)-1].ctx)

payload := bytes.NewBuffer([]byte{})
enc := json.NewEncoder(payload)
for _, q := range queue {
err := enc.Encode(q.event)
if err != nil {
rec.Debug("analytics: encode failed", progrock.ErrorLabel(err))
slog.Debug("analytics: encode failed", "error", err)
continue
}
}

req, err := http.NewRequest(http.MethodPost, trackURL, bytes.NewReader(payload.Bytes()))
if err != nil {
rec.Debug("analytics: new request failed", progrock.ErrorLabel(err))
slog.Debug("analytics: new request failed", "error", err)
return
}
if t.cfg.CloudToken != "" {
req.SetBasicAuth(t.cfg.CloudToken, "")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
rec.Debug("analytics: do request failed", progrock.ErrorLabel(err))
slog.Debug("analytics: do request failed", "error", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
rec.Debug("analytics: unexpected response", progrock.Labelf("status", resp.Status))
slog.Debug("analytics: unexpected response", "status", resp.Status)
}
}

Expand Down
24 changes: 9 additions & 15 deletions cmd/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/vito/progrock"

"dagger.io/dagger"
"github.com/dagger/dagger/cmd/codegen/generator"
Expand All @@ -17,18 +15,14 @@ import (
)

func Generate(ctx context.Context, cfg generator.Config, dag *dagger.Client) (err error) {
var vtxName string
logsW := os.Stdout

if cfg.ModuleName != "" {
vtxName = fmt.Sprintf("generating %s module: %s", cfg.Lang, cfg.ModuleName)
fmt.Fprintf(logsW, "generating %s module: %s\n", cfg.Lang, cfg.ModuleName)
} else {
vtxName = fmt.Sprintf("generating %s SDK client", cfg.Lang)
fmt.Fprintf(logsW, "generating %s SDK client\n", cfg.Lang)
}

ctx, vtx := progrock.Span(ctx, time.Now().String(), vtxName)
defer func() { vtx.Done(err) }()

logsW := vtx.Stdout()

var introspectionSchema *introspection.Schema
if cfg.IntrospectionJSON != "" {
var resp introspection.Response
Expand All @@ -55,12 +49,12 @@ func Generate(ctx context.Context, cfg generator.Config, dag *dagger.Client) (er

for _, cmd := range generated.PostCommands {
cmd.Dir = cfg.OutputDir
cmd.Stdout = vtx.Stdout()
cmd.Stderr = vtx.Stderr()
task := vtx.Task(strings.Join(cmd.Args, " "))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Fprintln(logsW, "running post-command:", strings.Join(cmd.Args, " "))
err := cmd.Run()
task.Done(err)
if err != nil {
fmt.Fprintln(logsW, "post-command failed:", err)
return err
}
}
Expand Down
6 changes: 5 additions & 1 deletion cmd/codegen/generator/go/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ func (g *GoGenerator) Generate(ctx context.Context, schema *introspection.Schema

var overlay fs.FS = mfs
if g.Config.ModuleName != "" {
overlay = layerfs.New(mfs, &MountedFS{FS: dagger.QueryBuilder, Name: "internal"})
overlay = layerfs.New(
mfs,
&MountedFS{FS: dagger.QueryBuilder, Name: "internal"},
&MountedFS{FS: dagger.Telemetry, Name: "internal"},
)
}

genSt := &generator.GeneratedState{
Expand Down
2 changes: 1 addition & 1 deletion cmd/codegen/generator/go/templates/module_interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ func (spec *parsedIfaceType) marshalJSONMethodCode() *Statement {
BlockFunc(func(g *Group) {
g.If(Id("r").Op("==").Nil()).Block(Return(Index().Byte().Parens(Lit(`""`)), Nil()))

g.List(Id("id"), Id("err")).Op(":=").Id("r").Dot("ID").Call(Qual("context", "Background").Call())
g.List(Id("id"), Id("err")).Op(":=").Id("r").Dot("ID").Call(Id("marshalCtx"))
g.If(Id("err").Op("!=").Nil()).Block(Return(Nil(), Id("err")))
g.Return(Id("json").Dot("Marshal").Call(Id("id")))
})
Expand Down
72 changes: 53 additions & 19 deletions cmd/codegen/generator/go/templates/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ from the Engine, calls the relevant function and returns the result. The generat
on the object+function name, with each case doing json deserialization of the input arguments and calling the actual
Go function.
*/
func (funcs goTemplateFuncs) moduleMainSrc() (string, error) {
func (funcs goTemplateFuncs) moduleMainSrc() (string, error) { //nolint: gocyclo
// HACK: the code in this func can be pretty flaky and tricky to debug -
// it's much easier to debug when we actually have stack traces, so we grab
// those on a panic
Expand Down Expand Up @@ -93,6 +93,12 @@ func (funcs goTemplateFuncs) moduleMainSrc() (string, error) {

tps := []types.Type{}
for _, obj := range objs {
// ignore any private definitions, they may be part of the runtime itself
// e.g. marshalCtx
if !obj.Exported() {
continue
}

// check if this is the constructor func, save it for later if so
if ok := ps.checkConstructor(obj); ok {
continue
Expand Down Expand Up @@ -230,58 +236,86 @@ const (
mainSrc = `func main() {
ctx := context.Background()
// Direct slog to the new stderr. This is only for dev time debugging, and
// runtime errors/warnings.
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelWarn,
})))
if err := dispatch(ctx); err != nil {
fmt.Println(err.Error())
os.Exit(2)
}
}
func dispatch(ctx context.Context) error {
ctx = telemetry.InitEmbedded(ctx, resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("dagger-go-sdk"),
// TODO version?
))
defer telemetry.Close()
ctx, span := Tracer().Start(ctx, "Go runtime",
trace.WithAttributes(
// In effect, the following two attributes hide the exec /runtime span.
//
// Replace the parent span,
attribute.Bool("dagger.io/ui.mask", true),
// and only show our children.
attribute.Bool("dagger.io/ui.passthrough", true),
))
defer span.End()
// A lot of the "work" actually happens when we're marshalling the return
// value, which entails getting object IDs, which happens in MarshalJSON,
// which has no ctx argument, so we use this lovely global variable.
setMarshalContext(ctx)
fnCall := dag.CurrentFunctionCall()
parentName, err := fnCall.ParentName(ctx)
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("get parent name: %w", err)
}
fnName, err := fnCall.Name(ctx)
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("get fn name: %w", err)
}
parentJson, err := fnCall.Parent(ctx)
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("get fn parent: %w", err)
}
fnArgs, err := fnCall.InputArgs(ctx)
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("get fn args: %w", err)
}
inputArgs := map[string][]byte{}
for _, fnArg := range fnArgs {
argName, err := fnArg.Name(ctx)
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("get fn arg name: %w", err)
}
argValue, err := fnArg.Value(ctx)
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("get fn arg value: %w", err)
}
inputArgs[argName] = []byte(argValue)
}
result, err := invoke(ctx, []byte(parentJson), parentName, fnName, inputArgs)
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("invoke: %w", err)
}
resultBytes, err := json.Marshal(result)
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("marshal: %w", err)
}
_, err = fnCall.ReturnValue(ctx, JSON(resultBytes))
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
return fmt.Errorf("store return value: %w", err)
}
return nil
}
`
parentJSONVar = "parentJSON"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import (
"context"
"log/slog"

"{{.PackageImport}}/internal/dagger"

"go.opentelemetry.io/otel/trace"
)

var dag = dagger.Connect()

func Tracer() trace.Tracer {
return otel.Tracer("dagger.io/sdk.go")
}

// used for local MarshalJSON implementations
var marshalCtx = context.Background()

// called by main()
func setMarshalContext(ctx context.Context) {
marshalCtx = ctx
dagger.SetMarshalContext(ctx)
}

type DaggerObject = dagger.DaggerObject

type ExecError = dagger.ExecError

{{ range .Types }}
{{ $name := .Name | FormatName }}
{{ $name := .Name | FormatName }}

{{ .Description | Comment }}
type {{ $name }} = dagger.{{ $name }}
Expand Down

0 comments on commit 6467198

Please sign in to comment.