diff --git a/.dagger/bench.go b/.dagger/bench.go index 64482b50e36..08d6848b508 100644 --- a/.dagger/bench.go +++ b/.dagger/bench.go @@ -24,8 +24,11 @@ func (b *Bench) All( // run benchmarks once with metrics tagged "prewarm" before running for real // +optional prewarm bool, + // notify this discord webhook on failure + // +optional + discordWebhook *dagger.Secret, ) error { - return b.bench( + return b.notifyOnFailure(ctx, b.bench( ctx, &benchOpts{ runTestRegex: "", @@ -38,7 +41,7 @@ func (b *Bench) All( testVerbose: testVerbose, prewarm: prewarm, }, - ) + ), discordWebhook) } func (b *Bench) Specific( @@ -69,8 +72,11 @@ func (b *Bench) Specific( // run benchmarks once with metrics tagged "prewarm" before running for real // +optional prewarm bool, + // notify this discord webhook on failure + // +optional + discordWebhook *dagger.Secret, ) error { - return b.bench( + return b.notifyOnFailure(ctx, b.bench( ctx, &benchOpts{ runTestRegex: run, @@ -83,7 +89,7 @@ func (b *Bench) Specific( testVerbose: testVerbose, prewarm: prewarm, }, - ) + ), discordWebhook) } type benchOpts struct { @@ -135,5 +141,37 @@ func (b *Bench) bench( } _, err = run(cmd).Sync(ctx) + + return err +} + +func (b *Bench) notifyOnFailure(ctx context.Context, err error, discordWebhook *dagger.Secret) error { + if err == nil { + return nil + } + if discordWebhook == nil { + return err + } + + commit, err := b.Test.Dagger.Git.Head().Commit(ctx) + if err != nil { + commit = "failed to find commit SHA" + } + + daggerCloudURL, err := dag.Notify().DaggerCloudTraceURL(ctx) + if err != nil { + return fmt.Errorf("failed to fetch trace URL for failed benchmarks: %w", err) + } + + message := fmt.Sprintf( + "[failed](%s) on SHA [%s](https://github.com/dagger/dagger/commit/%s)", + daggerCloudURL, + commit, + commit, + ) + _, discordErr := dag.Notify().Discord(ctx, discordWebhook, message) + if discordErr != nil { + return fmt.Errorf("failed to notify discord that benchmarks failed: %w, discord error %s", err, discordErr) + } return err } diff --git a/.dagger/sdk_java.go b/.dagger/sdk_java.go index 2ebf17a7bdb..64a8df9a863 100644 --- a/.dagger/sdk_java.go +++ b/.dagger/sdk_java.go @@ -106,20 +106,52 @@ func (t JavaSDK) Publish( return err } -var javaVersionRe = regexp.MustCompile(`([0-9\.\-a-zA-Z]+)<\/daggerengine\.version>`) +var stableVersionRe = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+$`) // Bump the Java SDK's Engine dependency func (t JavaSDK) Bump(ctx context.Context, version string) (*dagger.Directory, error) { - contents, err := t.Dagger.Source().File(javaSDKVersionPomPath).Contents(ctx) - if err != nil { - return nil, err + version = strings.TrimPrefix(version, "v") + v := version + if !stableVersionRe.MatchString(v) { + v = fmt.Sprintf("%s-SNAPSHOT", v) } + bumpCtr := t.Maven(ctx). + WithExec([]string{ + "mvn", + "versions:set", + "-DgenerateBackupPoms=false", + "-DnewVersion=" + v, + }). + WithExec([]string{ + "mvn", + "versions:set-property", + "-DgenerateBackupPoms=false", + "-Dproperty=daggerengine.version", + "-DnewVersion=" + version, + }). + WithWorkdir("runtime/template"). + WithExec([]string{ + "mvn", + "versions:set-property", + "-DgenerateBackupPoms=false", + "-Dproperty=dagger.module.deps", + "-DnewVersion=" + version + "-template-module", + }) - newVersion := fmt.Sprintf(`%s`, strings.TrimPrefix(version, "v")) - newContents := javaVersionRe.ReplaceAllString(contents, newVersion) + poms := []string{ + "/sdk/java/dagger-codegen-maven-plugin/pom.xml", + "/sdk/java/dagger-java-annotation-processor/pom.xml", + "/sdk/java/dagger-java-sdk/pom.xml", + "/sdk/java/dagger-java-samples/pom.xml", + "/sdk/java/pom.xml", + "/sdk/java/runtime/template/pom.xml", + } - dir := dag.Directory().WithNewFile(javaSDKVersionPomPath, newContents) - return dir, nil + bumped := dag.Directory() + for _, pom := range poms { + bumped = bumped.WithFile(pom, bumpCtr.File(pom)) + } + return bumped, nil } // Bump dependencies in the Java SDK diff --git a/.github/workflows/benchmark-engine.yml b/.github/workflows/benchmark-engine.yml index b4fea598872..cf11adf197d 100644 --- a/.github/workflows/benchmark-engine.yml +++ b/.github/workflows/benchmark-engine.yml @@ -32,7 +32,9 @@ jobs: run: dagger core version - name: Benchmark Engine uses: ./.github/actions/call + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} with: - function: bench all --prewarm + function: bench all --prewarm ${{ github.event_name != 'pull_request' && '--discord-webhook="op://releng/Discord Webhook - Engine Benchmarks/credential"' || '' }} dev-engine: false upload-logs: true diff --git a/README.md b/README.md index 6116b54e640..b4e8d6dbf06 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,19 @@ Dagger is an open-source runtime for composable workflows. It's perfect for syst ## Key Features -- **Reproducible execution engine**, powered by containerized functions and a declarative DAG scheduler. +- **Containerized Workflow Execution:** Transform code into containerized, composable operations. Build reproducible workflows in any language with custom environments, parallel processing, and seamless chaining. -- **Universal type system**, for strongly typed composition and discovery, across platforms and languages. +- **Universal Type System:** Mix and match components from any language with type-safe connections. Use the best tools from each ecosystem without translation headaches. -- **Powerful data layer**: out-of-the-box caching, immutable state, and data tracability. +- **Automatic Artifact Caching:** Operations produce cacheable, immutable artifacts — even for LLMs and API calls. Your workflows run faster and cost less. -- **Native SDKs for 5 languages**. Go, Typescript, Python, PHP, Java - and more on the way. +- **Built-in Observability:** Full visibility into operations with tracing, logs, and metrics. Debug complex workflows and know exactly what's happening. -- **Open ecosystem**: [Thousands of modules](https://daggerverse.dev) at your fingertips, all interoperable across languages and platforms. +- **Open Platform:** Works with any compute platform and tech stack — today and tomorrow. Ship faster, experiment freely, and don’t get locked into someone else's choices. -- **Interactive command-line environment**, for rapid prototyping and debugging. +- **LLM Augmentation:** Native integration of any LLM that automatically discovers and uses available functions in your workflow. Ship mind-blowing agents in just a few dozen lines of code. -- **Batteries-included observability**. Deep tracing, metrics (including token count), and logs, all accessible from the CLI or a web UI. - -- **Adapts to you**. Seamlessly integrate with all major compute and storage platforms, CI systems, languages, and agent frameworks. - -- **LLM augmentation**. Connect to any LLM endpoint (OpenAI, Google, Anthropic, LLama, DeepSeek, etc.) and give it access to your Dagger objects. Dagger automatically handles the agentic loop. No complicated framework needed. +- **Interactive Terminal:** Directly interact with your workflow or agents in real-time through your terminal. Prototype, test, debug, and ship even faster. diff --git a/cmd/codegen/generator/generator.go b/cmd/codegen/generator/generator.go index 5d5084acc2a..8086d0e8e10 100644 --- a/cmd/codegen/generator/generator.go +++ b/cmd/codegen/generator/generator.go @@ -54,9 +54,9 @@ type Config struct { // ClientOnly indicates that the codegen should only generate the client code. ClientOnly bool - // LocalSDK indicates that the codegen should use the local SDK instead of the published one. + // Dev indicates that the codegen should use the local SDK instead of the published one. // This is only relevant when ClientOnly is true. - LocalSDK bool + Dev bool } type Generator interface { diff --git a/cmd/codegen/generator/go/generator.go b/cmd/codegen/generator/go/generator.go index 5c15ece1d54..68d94fc4ba1 100644 --- a/cmd/codegen/generator/go/generator.go +++ b/cmd/codegen/generator/go/generator.go @@ -149,8 +149,8 @@ func (g *GoGenerator) GenerateClient(ctx context.Context, schema *introspection. // Use the published package library for external dagger packages. packageImport := "dagger.io/dagger" - // If localSDK is needed, we need to add local files to the overlay and change the package import - if g.Config.LocalSDK { + // If dev is set, we need to add local files to the overlay and change the package import + if g.Config.Dev { layers = append( layers, &MountedFS{FS: dagger.QueryBuilder, Name: "internal"}, @@ -158,7 +158,8 @@ func (g *GoGenerator) GenerateClient(ctx context.Context, schema *introspection. ) // Get the go package from the module - pkg, _, err := loadPackage(ctx, "/module") + // We assume that we'll be located at the root source directory + pkg, _, err := loadPackage(ctx, ".") if err != nil { return nil, fmt.Errorf("load package %q: %w", outDir, err) } diff --git a/cmd/codegen/generator/go/templates/functions.go b/cmd/codegen/generator/go/templates/functions.go index bf8e5d13893..49ee25a46bb 100644 --- a/cmd/codegen/generator/go/templates/functions.go +++ b/cmd/codegen/generator/go/templates/functions.go @@ -82,7 +82,7 @@ func (funcs goTemplateFuncs) FuncMap() template.FuncMap { "IsPartial": funcs.isPartial, "IsModuleCode": funcs.isModuleCode, "IsStandaloneClient": funcs.isStandaloneClient, - "IsLocalSDK": funcs.isLocalSDK, + "IsDevMode": funcs.isDevMode, "ModuleMainSrc": funcs.moduleMainSrc, "ModuleRelPath": funcs.moduleRelPath, } diff --git a/cmd/codegen/generator/go/templates/modules.go b/cmd/codegen/generator/go/templates/modules.go index c340c7154b0..558e1065c5f 100644 --- a/cmd/codegen/generator/go/templates/modules.go +++ b/cmd/codegen/generator/go/templates/modules.go @@ -34,8 +34,8 @@ func (funcs goTemplateFuncs) isStandaloneClient() bool { return funcs.cfg.ClientOnly } -func (funcs goTemplateFuncs) isLocalSDK() bool { - return funcs.cfg.LocalSDK +func (funcs goTemplateFuncs) isDevMode() bool { + return funcs.cfg.Dev } func (funcs goTemplateFuncs) moduleRelPath(path string) string { diff --git a/cmd/codegen/generator/go/templates/src/_dagger.gen.go/defs.go.tmpl b/cmd/codegen/generator/go/templates/src/_dagger.gen.go/defs.go.tmpl index b21310e8556..f97cd7bb6b6 100644 --- a/cmd/codegen/generator/go/templates/src/_dagger.gen.go/defs.go.tmpl +++ b/cmd/codegen/generator/go/templates/src/_dagger.gen.go/defs.go.tmpl @@ -21,13 +21,13 @@ import ( "{{.PackageImport}}/internal/telemetry" {{ end }} -{{ if IsLocalSDK }} +{{ if IsDevMode }} "{{.PackageImport}}/internal/querybuilder" "{{.PackageImport}}/internal/engineconn" {{ end }} {{- if IsStandaloneClient }} - {{ if not IsLocalSDK }} + {{ if not IsDevMode }} "dagger.io/dagger/querybuilder" {{ end }} "dagger.io/dagger" @@ -228,7 +228,7 @@ func (c errorWrappedClient) MakeRequest(ctx context.Context, req *graphql.Reques {{ end }} {{ if IsStandaloneClient }} -{{ if IsLocalSDK }} +{{ if IsDevMode }} {{ template "_dagger.gen.go/client.go.tmpl" . }} {{ else }} type Client struct { diff --git a/cmd/codegen/generator/typescript/templates/functions.go b/cmd/codegen/generator/typescript/templates/functions.go index ef4524e57c1..d6c693dda85 100644 --- a/cmd/codegen/generator/typescript/templates/functions.go +++ b/cmd/codegen/generator/typescript/templates/functions.go @@ -66,7 +66,7 @@ func (funcs typescriptTemplateFuncs) FuncMap() template.FuncMap { "ModuleRelPath": funcs.moduleRelPath, "FormatProtected": funcs.formatProtected, "IsClientOnly": funcs.isClientOnly, - "IsLocalSDK": funcs.isLocalSDK, + "IsDevMode": funcs.isDevMode, } } @@ -326,6 +326,6 @@ func (funcs typescriptTemplateFuncs) isClientOnly() bool { return funcs.cfg.ClientOnly } -func (funcs typescriptTemplateFuncs) isLocalSDK() bool { - return funcs.cfg.LocalSDK +func (funcs typescriptTemplateFuncs) isDevMode() bool { + return funcs.cfg.Dev } diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go index 999109e9748..d5ff92ed3c7 100644 --- a/cmd/codegen/main.go +++ b/cmd/codegen/main.go @@ -28,8 +28,8 @@ var ( clientOnly bool - localSDK bool - isInit bool + dev bool + isInit bool ) var rootCmd = &cobra.Command{ @@ -57,7 +57,7 @@ func init() { rootCmd.Flags().BoolVar(&merge, "merge", false, "merge module deps with project's existing go.mod in a parent directory") rootCmd.Flags().BoolVar(&isInit, "is-init", false, "whether this command is initializing a new module") rootCmd.Flags().BoolVar(&clientOnly, "client-only", false, "generate only client code") - rootCmd.Flags().BoolVar(&localSDK, "local-sdk", false, "use local SDK dependency") + rootCmd.Flags().BoolVar(&dev, "dev", false, "generate in dev mode") introspectCmd.Flags().StringVarP(&outputSchema, "output", "o", "", "save introspection result to file") rootCmd.AddCommand(introspectCmd) @@ -73,7 +73,7 @@ func ClientGen(cmd *cobra.Command, args []string) error { Merge: merge, IsInit: isInit, ClientOnly: clientOnly, - LocalSDK: localSDK, + Dev: dev, } if moduleName != "" { diff --git a/cmd/dagger/client_add.go b/cmd/dagger/client_add.go index 281a4b60068..fcfd23714d0 100644 --- a/cmd/dagger/client_add.go +++ b/cmd/dagger/client_add.go @@ -13,12 +13,15 @@ import ( var ( generator string - localSDK bool + dev bool ) func init() { clientAddCmd.Flags().StringVar(&generator, "generator", "", "Generator to use to generate the client") - clientAddCmd.Flags().BoolVar(&localSDK, "local-sdk", false, "Use local SDK dependency") + clientAddCmd.Flags().BoolVar(&dev, "dev", false, "Generate in developer mode") + + // Hide `dev` flag since it's only for maintainers. + _ = clientAddCmd.Flags().MarkHidden("dev") } var clientAddCmd = &cobra.Command{ @@ -75,9 +78,17 @@ func (c *clientAddHandler) Run(ctx context.Context) (rerr error) { return fmt.Errorf("failed to initialize client generator module: %w", err) } - _, err = mod.Source.GenerateClient(generator, c.outputPath, dagger.ModuleSourceGenerateClientOpts{ - LocalSDK: localSDK, - }).Export(ctx, ".") + contextDirPath, err := mod.Source.LocalContextDirectoryPath(ctx) + if err != nil { + return fmt.Errorf("failed to get local context directory path: %w", err) + } + + _, err = mod.Source. + WithClient(generator, c.outputPath, dagger.ModuleSourceWithClientOpts{ + Dev: dev, + }). + GeneratedContextDirectory(). + Export(ctx, contextDirPath) if err != nil { return fmt.Errorf("failed to export client: %w", err) } diff --git a/cmd/dagger/module.go b/cmd/dagger/module.go index 2d5542b329d..7c268fd23e7 100644 --- a/cmd/dagger/module.go +++ b/cmd/dagger/module.go @@ -539,11 +539,17 @@ This command is idempotent: you can run it at any time, any number of times. It developSourcePath = filepath.Join(srcRootAbsPath, inferredSourcePath) } + clients, err := modSrc.ConfigClients(ctx) + if err != nil { + return fmt.Errorf("failed to get module clients configuration: %w", err) + } + // if there's no SDK and the user isn't changing the source path, there's nothing to do. // error out rather than silently doing nothing. - if modSDK == "" && developSourcePath == "" { - return fmt.Errorf("dagger develop on a module without an SDK requires either --sdk or --source") + if modSDK == "" && developSourcePath == "" && len(clients) == 0 { + return fmt.Errorf("dagger develop on a module without an SDK or clients requires either --sdk or --source") } + if developSourcePath != "" { // ensure source path is relative to the source root sourceAbsPath, err := pathutil.Abs(developSourcePath) diff --git a/core/integration/client_generator_test.go b/core/integration/client_generator_test.go index bba28274d72..e141dfa6ee2 100644 --- a/core/integration/client_generator_test.go +++ b/core/integration/client_generator_test.go @@ -2,11 +2,13 @@ package core import ( "context" + "encoding/json" "fmt" "strings" "testing" "dagger.io/dagger" + "github.com/dagger/dagger/core/modules" "github.com/dagger/testctx" "github.com/stretchr/testify/require" ) @@ -494,6 +496,267 @@ main() }) } +func (ClientGeneratorTest) TestPersistence(ctx context.Context, t *testctx.T) { + t.Run("work without a module implementation", func(ctx context.Context, t *testctx.T) { + type testCase struct { + baseImage string + generator string + callCmd []string + setup dagger.WithContainerFunc + postSetup dagger.WithContainerFunc + } + + testCases := []testCase{ + { + baseImage: golangImage, + generator: "go", + callCmd: []string{"go", "run", "main.go"}, + setup: func(ctr *dagger.Container) *dagger.Container { + return ctr. + WithExec([]string{"go", "mod", "init", "test.com/test"}). + WithNewFile("main.go", `package main + + import ( + "context" + "fmt" + + "test.com/test/dagger" + ) + + func main() { + ctx := context.Background() + + dag, err := dagger.Connect(ctx) + if err != nil { + panic(err) + } + + res, err := dag.Hello().Hello(ctx) + if err != nil { + panic(err) + } + + fmt.Println("result:", res) + } + `, + ) + }, + postSetup: func(ctr *dagger.Container) *dagger.Container { + return ctr.WithoutDirectory("dagger") + }, + }, + { + baseImage: nodeImage, + generator: "typescript", + setup: func(ctr *dagger.Container) *dagger.Container { + return ctr. + WithExec([]string{"npm", "install", "-g", "tsx@4.15.6"}). + WithExec([]string{"npm", "init", "-y"}). + WithExec([]string{"npm", "pkg", "set", "type=module"}). + WithExec([]string{"npm", "install", "-D", "typescript"}). + WithNewFile("index.ts", `import { connection, dag } from "@dagger.io/client" + +async function main() { + await connection(async () => { + const res = await dag.hello().hello() + + console.log("result:", res) + }) +} + +main() +`) + }, + postSetup: func(ctr *dagger.Container) *dagger.Container { + return ctr. + WithExec([]string{"npm", "install"}). + WithoutDirectory("dagger") + }, + callCmd: []string{"tsx", "index.ts"}, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.generator, func(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + moduleSrc := c.Container().From(tc.baseImage). + WithMountedFile(testCLIBinPath, daggerCliFile(t, c)). + WithWorkdir("/work"). + WithEnvVariable("_EXPERIMENTAL_DAGGER_CLI_BIN", "/bin/dagger"). + With(nonNestedDevEngine(c)). + With(daggerNonNestedExec("init")). + With(daggerNonNestedExec("install", "github.com/shykes/hello@2d789671a44c4d559be506a9bc4b71b0ba6e23c9")). + With(tc.setup). + With(daggerClientAdd(tc.generator)). + With(tc.postSetup) + + modCfgContents, err := moduleSrc. + File("dagger.json"). + Contents(ctx) + require.NoError(t, err) + + var modCfg modules.ModuleConfig + require.NoError(t, json.Unmarshal([]byte(modCfgContents), &modCfg)) + require.Equal(t, 1, len(modCfg.Clients)) + require.Equal(t, tc.generator, modCfg.Clients[0].Generator) + require.Equal(t, "dagger", modCfg.Clients[0].Directory) + + // Execute module after regeneration + out, err := moduleSrc. + With(daggerNonNestedExec("develop")). + With(daggerNonNestedRun(tc.callCmd...)). + Stdout(ctx) + + require.NoError(t, err) + require.Equal(t, "result: hello, world!\n", out) + }) + } + }) + + t.Run("cohexist with a module implementation", func(ctx context.Context, t *testctx.T) { + type testCase struct { + baseImage string + generator string + callCmd []string + setup dagger.WithContainerFunc + postSetup dagger.WithContainerFunc + } + + testCases := []testCase{ + { + baseImage: golangImage, + generator: "go", + callCmd: []string{"go", "run", "main.go"}, + setup: func(ctr *dagger.Container) *dagger.Container { + return ctr. + With(daggerNonNestedExec("init", "--name=test", "--sdk=go", "--source=.dagger")). + WithNewFile(".dagger/main.go", `package main + + type Test struct{} + + func (t *Test) Hello() string { + return "hello" + } + `). + WithExec([]string{"go", "mod", "init", "test.com/test"}). + WithNewFile("main.go", `package main + + import ( + "context" + "fmt" + + "test.com/test/dagger" + ) + + func main() { + ctx := context.Background() + + dag, err := dagger.Connect(ctx) + if err != nil { + panic(err) + } + + res, err := dag.Test().Hello(ctx) + if err != nil { + panic(err) + } + + fmt.Println("result:", res) + } + `, + ) + }, + postSetup: func(ctr *dagger.Container) *dagger.Container { + // Remove generated files so they can be regenerated using dagger develop + return ctr.WithoutDirectory("dagger").WithoutFile(".dagger/dagger.gen.go") + }, + }, + { + baseImage: nodeImage, + generator: "typescript", + setup: func(ctr *dagger.Container) *dagger.Container { + return ctr. + With(daggerNonNestedExec("init", "--name=test", "--sdk=typescript", "--source=.dagger")). + WithNewFile(".dagger/src/index.ts", `import { object, func } from '@dagger.io/dagger' + + @object() + export class Test { + @func() + hello(): string { + return 'hello' + } + } + `). + WithExec([]string{"npm", "install", "-g", "tsx@4.15.6"}). + WithExec([]string{"npm", "init", "-y"}). + WithExec([]string{"npm", "pkg", "set", "type=module"}). + WithExec([]string{"npm", "install", "-D", "typescript"}). + WithNewFile("index.ts", `import { connection, dag } from "@dagger.io/client" + + async function main() { + await connection(async () => { + const res = await dag.test().hello() + + console.log("result:", res) + }) + } + + main() + `) + }, + postSetup: func(ctr *dagger.Container) *dagger.Container { + // Remove generated files so they can be regenerated using dagger develop + return ctr. + WithExec([]string{"npm", "install"}). + WithoutDirectory("dagger"). + WithoutDirectory(".dagger/sdk") + }, + callCmd: []string{"tsx", "index.ts"}, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.generator, func(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + moduleSrc := c.Container().From(tc.baseImage). + WithMountedFile(testCLIBinPath, daggerCliFile(t, c)). + WithWorkdir("/work"). + WithEnvVariable("_EXPERIMENTAL_DAGGER_CLI_BIN", "/bin/dagger"). + With(nonNestedDevEngine(c)). + With(tc.setup). + With(daggerClientAdd(tc.generator)). + With(tc.postSetup) + + modCfgContents, err := moduleSrc. + File("dagger.json"). + Contents(ctx) + require.NoError(t, err) + + var modCfg modules.ModuleConfig + require.NoError(t, json.Unmarshal([]byte(modCfgContents), &modCfg)) + require.Equal(t, 1, len(modCfg.Clients)) + require.Equal(t, tc.generator, modCfg.Clients[0].Generator) + require.Equal(t, "dagger", modCfg.Clients[0].Directory) + + // Execute module after regeneration + out, err := moduleSrc. + With(daggerNonNestedExec("develop")). + With(daggerNonNestedRun(tc.callCmd...)). + Stdout(ctx) + + require.NoError(t, err) + require.Equal(t, "result: hello\n", out) + }) + } + }) +} + func (ClientGeneratorTest) TestOutputDir(ctx context.Context, t *testctx.T) { type testSetup struct { baseImage string @@ -744,6 +1007,7 @@ func (ClientGeneratorTest) TestCustomClientGenerator(ctx context.Context, t *tes testCases := []testCase{ { generatorSDK: "go", + // Omit `dev` from signature to verify that it works if it's not defined. generatorSource: `package main import ( @@ -761,13 +1025,13 @@ func (g *Generator) GenerateClient( ctx context.Context, modSource *dagger.ModuleSource, introspectionJSON *dagger.File, - useLocalSdk bool, ) (*dagger.Directory, error) { return dag.Directory().WithNewFile("hello.txt", "hello world"), nil }`, }, { generatorSDK: "typescript", + // Omit `dev` from signature to verify that it works if it's not defined. generatorSource: `import { dag, Directory, object, func, ModuleSource, File } from "@dagger.io/dagger" @object() @@ -781,7 +1045,6 @@ export class Generator { generateClient( modSource: ModuleSource, introspectionJSON: File, - useLocalSdk: boolean, ): Directory { return dag.directory().withNewFile("hello.txt", "hello world") } @@ -802,7 +1065,7 @@ export class Generator { With(sdkSource(tc.generatorSDK, tc.generatorSource)). WithWorkdir("/work"). With(daggerExec("init")). - With(daggerClientAdd("./generator")) + With(daggerExec("client", "add", "--generator=./generator")) out, err := moduleSrc.File("hello.txt").Contents(ctx) require.NoError(t, err) diff --git a/core/integration/engine_test.go b/core/integration/engine_test.go index e2fa8e9793f..f3139a43ab4 100644 --- a/core/integration/engine_test.go +++ b/core/integration/engine_test.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "fmt" + "net" + "net/http" "os" "path/filepath" "slices" @@ -659,3 +661,108 @@ func (EngineSuite) TestModuleVersionCompatInvalid(ctx context.Context, t *testct require.Error(t, err) requireErrOut(t, err, `module requires dagger v100.0.0, but you have`) } + +func (EngineSuite) TestConcurrentCallContextCanceled(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + l, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + t.Cleanup(func() { + l.Close() + }) + port := l.Addr().(*net.TCPAddr).Port + + hitCh := make(chan struct{}, 1) + allDoneCh := make(chan struct{}) + httpSrv := http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + select { + case hitCh <- struct{}{}: + default: + } + + select { + case <-allDoneCh: + w.Write([]byte("done")) + default: + w.Write([]byte("hi")) + } + }), + BaseContext: func(net.Listener) context.Context { + return ctx + }, + } + go httpSrv.Serve(l) + + httpSvc := c.Host().Service([]dagger.PortForward{{ + Backend: port, + Frontend: port, + }}) + + ctr, err := c.Container().From(alpineImage). + WithExec([]string{"apk", "add", "curl"}). + WithServiceBinding("srv", httpSvc). + WithEnvVariable("PORT", fmt.Sprintf("%d", port)). + WithEnvVariable("CACHEBUSTER", identity.NewID()). + Sync(ctx) + require.NoError(t, err) + ctr = ctr.WithExec([]string{"sh", "-c", + // request http://srv:$PORT/ in a loop until it returns "done" + "until [ \"$(curl -s http://srv:$PORT/)\" = \"done\" ]; do sleep 1; done", + }) + + ctx1, cancel1 := context.WithCancel(ctx) + errCh1 := make(chan error, 1) + go func() { + defer close(errCh1) + _, err := ctr.Sync(ctx1) + errCh1 <- err + }() + // wait for the first hit to the server so we know the exec is running + select { + case <-hitCh: + case <-time.After(60 * time.Second): // extremely generous for when the engine is very slow under load + } + + // start the second duped exec + errCh2 := make(chan error, 1) + go func() { + defer close(errCh2) + _, err := ctr.Sync(ctx) + errCh2 <- err + }() + + // give the second dupe exec some time to start running + for range 3 { + select { + case <-hitCh: + case <-time.After(60 * time.Second): + } + } + + // cancel the first exec, verify it errors + cancel1() + select { + case err := <-errCh1: + require.ErrorIs(t, err, context.Canceled) + case <-time.After(60 * time.Second): + t.Fatal("timed out waiting for errCh1") + } + + // make sure the exec is still running despite cancelation of first client + for range 2 { + select { + case <-hitCh: + case <-time.After(60 * time.Second): + } + } + + // tell the exec to break its loop and exit, verify no error despite earlier cancelation + close(allDoneCh) + select { + case err := <-errCh2: + require.NoError(t, err) + case <-time.After(60 * time.Second): + t.Fatal("timed out waiting for errCh2") + } +} diff --git a/core/integration/module_java_test.go b/core/integration/module_java_test.go index 5c0ea5c36b7..baa329cb41f 100644 --- a/core/integration/module_java_test.go +++ b/core/integration/module_java_test.go @@ -65,7 +65,7 @@ func (JavaSuite) TestInit(_ context.Context, t *testctx.T) { } func (JavaSuite) TestFields(_ context.Context, t *testctx.T) { - t.Run("can set and retrieve field", func(ctx context.Context, t *testctx.T) { + t.Run("can set and retrieve field using custom function", func(ctx context.Context, t *testctx.T) { c := connect(ctx, t) out, err := javaModule(t, c, "fields"). @@ -75,6 +75,49 @@ func (JavaSuite) TestFields(_ context.Context, t *testctx.T) { require.NoError(t, err) require.Contains(t, out, "a.b.c") }) + + t.Run("can set and retrieve field using direct access to the field when decorated", func(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + out, err := javaModule(t, c, "fields"). + With(daggerShell("with-version a.b.c | version")). + Stdout(ctx) + + require.NoError(t, err) + require.Contains(t, out, "a.b.c") + }) + + t.Run("can set and retrieve public field using direct access to the field", func(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + out, err := javaModule(t, c, "fields"). + With(daggerShell("with-version a.b.c | public-version")). + Stdout(ctx) + + require.NoError(t, err) + require.Contains(t, out, "a.b.c") + }) + + t.Run("can set and retrieve non exposed field using custom function", func(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + out, err := javaModule(t, c, "fields"). + With(daggerShell("with-version a.b.c | get-internal-version")). + Stdout(ctx) + + require.NoError(t, err) + require.Contains(t, out, "a.b.c") + }) + + t.Run("can set but not retrieve non exposed field using direct access to the field", func(ctx context.Context, t *testctx.T) { + c := connect(ctx, t) + + _, err := javaModule(t, c, "fields"). + With(daggerShell("with-version a.b.c | internal-version")). + Stdout(ctx) + + require.Error(t, err) + }) } func (JavaSuite) TestDefaultValue(_ context.Context, t *testctx.T) { diff --git a/core/integration/module_test.go b/core/integration/module_test.go index af0d0e5e1d3..209026d8ae0 100644 --- a/core/integration/module_test.go +++ b/core/integration/module_test.go @@ -5411,11 +5411,11 @@ func daggerNonNestedRun(args ...string) dagger.WithContainerFunc { } func daggerClientAdd(generator string) dagger.WithContainerFunc { - return daggerExec("client", "add", "--generator="+generator, "--local-sdk") + return daggerExec("client", "add", "--generator="+generator, "--dev") } func daggerClientAddAt(generator string, outputDirPath string) dagger.WithContainerFunc { - return daggerExec("client", "add", "--generator="+generator, "--local-sdk", outputDirPath) + return daggerExec("client", "add", "--generator="+generator, "--dev", outputDirPath) } func daggerQuery(query string, args ...any) dagger.WithContainerFunc { @@ -5480,7 +5480,7 @@ func privateRepoSetup(c *dagger.Client, t *testctx.T, tc vcsTestCase) (dagger.Wi WithExec([]string{ "git", "config", "--global", "credential.https://" + tc.expectedHost + ".helper", - `!f() { test "$1" = get && echo "password=` + token + `"; }; f`, + `!f() { test "$1" = get && echo -e "password=` + token + `\nusername=git"; }; f`, }) } diff --git a/core/integration/testdata/modules/java/construct/dagger.json b/core/integration/testdata/modules/java/construct/dagger.json index bae337734cd..2ed579c6926 100644 --- a/core/integration/testdata/modules/java/construct/dagger.json +++ b/core/integration/testdata/modules/java/construct/dagger.json @@ -1,6 +1,6 @@ { "name": "construct", - "engineVersion": "v0.16.0-010101000000-dev-fa7b6d7d95fe", + "engineVersion": "v0.16.3-010101000000-dev-43f71432100c", "sdk": { "source": "../../sdk/java" } diff --git a/core/integration/testdata/modules/java/construct/pom.xml b/core/integration/testdata/modules/java/construct/pom.xml index fccd4c1da47..df2fa625096 100644 --- a/core/integration/testdata/modules/java/construct/pom.xml +++ b/core/integration/testdata/modules/java/construct/pom.xml @@ -12,18 +12,19 @@ 17 UTF-8 + 0.16.3-construct-module io.dagger dagger-java-sdk - 1.0.0-SNAPSHOT + ${dagger.module.deps} io.dagger dagger-java-annotation-processor - 1.0.0-SNAPSHOT + ${dagger.module.deps} provided @@ -63,6 +64,10 @@ 3.3.1 + maven-compiler-plugin 3.13.0 @@ -71,7 +76,7 @@ io.dagger dagger-java-annotation-processor - 1.0.0-SNAPSHOT + ${dagger.module.deps} @@ -108,6 +113,35 @@ + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + add-generated-sources + + add-source + + + + ${project.build.directory}/generated-sources/dagger-io + ${project.build.directory}/generated-sources/dagger-module + ${project.build.directory}/generated-sources/entrypoint + + + + + + + org.apache.maven.plugins maven-shade-plugin 3.5.0 @@ -131,4 +165,4 @@ - \ No newline at end of file + diff --git a/core/integration/testdata/modules/java/construct/src/main/java/io/dagger/modules/construct/Construct.java b/core/integration/testdata/modules/java/construct/src/main/java/io/dagger/modules/construct/Construct.java index 33dafa71c8c..ad2976d1dce 100644 --- a/core/integration/testdata/modules/java/construct/src/main/java/io/dagger/modules/construct/Construct.java +++ b/core/integration/testdata/modules/java/construct/src/main/java/io/dagger/modules/construct/Construct.java @@ -1,12 +1,13 @@ package io.dagger.modules.construct; -import io.dagger.module.AbstractModule; +import static io.dagger.client.Dagger.dag; + import io.dagger.module.annotation.Default; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object -public class Construct extends AbstractModule { +public class Construct { public String value; public Construct() { diff --git a/core/integration/testdata/modules/java/defaults/dagger.json b/core/integration/testdata/modules/java/defaults/dagger.json index 0f8ecb7233f..22b89fa01e0 100644 --- a/core/integration/testdata/modules/java/defaults/dagger.json +++ b/core/integration/testdata/modules/java/defaults/dagger.json @@ -1,6 +1,6 @@ { "name": "defaults", - "engineVersion": "v0.16.0-010101000000-dev-ccad89615976", + "engineVersion": "v0.16.3-010101000000-dev-43f71432100c", "sdk": { "source": "../../sdk/java" } diff --git a/core/integration/testdata/modules/java/defaults/pom.xml b/core/integration/testdata/modules/java/defaults/pom.xml index 9311e49bcc6..e00cdc8f10c 100644 --- a/core/integration/testdata/modules/java/defaults/pom.xml +++ b/core/integration/testdata/modules/java/defaults/pom.xml @@ -12,18 +12,19 @@ 17 UTF-8 + 0.16.3-defaults-module io.dagger dagger-java-sdk - 1.0.0-SNAPSHOT + ${dagger.module.deps} io.dagger dagger-java-annotation-processor - 1.0.0-SNAPSHOT + ${dagger.module.deps} provided @@ -63,6 +64,10 @@ 3.3.1 + maven-compiler-plugin 3.13.0 @@ -71,7 +76,7 @@ io.dagger dagger-java-annotation-processor - 1.0.0-SNAPSHOT + ${dagger.module.deps} @@ -108,6 +113,35 @@ + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + add-generated-sources + + add-source + + + + ${project.build.directory}/generated-sources/dagger-io + ${project.build.directory}/generated-sources/dagger-module + ${project.build.directory}/generated-sources/entrypoint + + + + + + + org.apache.maven.plugins maven-shade-plugin 3.5.0 diff --git a/core/integration/testdata/modules/java/defaults/src/main/java/io/dagger/modules/defaults/Defaults.java b/core/integration/testdata/modules/java/defaults/src/main/java/io/dagger/modules/defaults/Defaults.java index f258a12f387..962926fecb6 100644 --- a/core/integration/testdata/modules/java/defaults/src/main/java/io/dagger/modules/defaults/Defaults.java +++ b/core/integration/testdata/modules/java/defaults/src/main/java/io/dagger/modules/defaults/Defaults.java @@ -3,7 +3,6 @@ import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; import io.dagger.client.File; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Default; import io.dagger.module.annotation.DefaultPath; import io.dagger.module.annotation.Function; @@ -13,10 +12,8 @@ import java.util.Optional; @Object -public class Defaults extends AbstractModule { - public Defaults() { - super(); - } +public class Defaults { + public Defaults() {} @Function public String echo(@Default("default value") String value) { diff --git a/core/integration/testdata/modules/java/fields/dagger.json b/core/integration/testdata/modules/java/fields/dagger.json index 108f2268b5d..b16b91fad4e 100644 --- a/core/integration/testdata/modules/java/fields/dagger.json +++ b/core/integration/testdata/modules/java/fields/dagger.json @@ -1,5 +1,7 @@ { "name": "fields", - "engineVersion": "v0.15.4-010101000000-dev-9fc7291f4b92", - "sdk": "java" + "engineVersion": "v0.16.3-010101000000-dev-43f71432100c", + "sdk": { + "source": "../../sdk/java" + } } diff --git a/core/integration/testdata/modules/java/fields/pom.xml b/core/integration/testdata/modules/java/fields/pom.xml index 47cd7d23c3b..6475b9d9aca 100644 --- a/core/integration/testdata/modules/java/fields/pom.xml +++ b/core/integration/testdata/modules/java/fields/pom.xml @@ -12,18 +12,19 @@ 17 UTF-8 + 0.16.3-fields-module io.dagger dagger-java-sdk - 1.0.0-SNAPSHOT + ${dagger.module.deps} io.dagger dagger-java-annotation-processor - 1.0.0-SNAPSHOT + ${dagger.module.deps} provided @@ -46,6 +47,10 @@ 3.3.1 + maven-compiler-plugin 3.13.0 @@ -54,7 +59,7 @@ io.dagger dagger-java-annotation-processor - 1.0.0-SNAPSHOT + ${dagger.module.deps} @@ -91,6 +96,35 @@ + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + add-generated-sources + + add-source + + + + ${project.build.directory}/generated-sources/dagger-io + ${project.build.directory}/generated-sources/dagger-module + ${project.build.directory}/generated-sources/entrypoint + + + + + + + org.apache.maven.plugins maven-shade-plugin 3.5.0 diff --git a/core/integration/testdata/modules/java/fields/src/main/java/io/dagger/modules/fields/Fields.java b/core/integration/testdata/modules/java/fields/src/main/java/io/dagger/modules/fields/Fields.java index f7dd6878476..b7dfd407cb9 100644 --- a/core/integration/testdata/modules/java/fields/src/main/java/io/dagger/modules/fields/Fields.java +++ b/core/integration/testdata/modules/java/fields/src/main/java/io/dagger/modules/fields/Fields.java @@ -1,20 +1,24 @@ package io.dagger.modules.fields; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object -public class Fields extends AbstractModule { - public String version; +public class Fields { + @Function + private String version; - public Fields() { - super(); - } + public String publicVersion; + + private String internalVersion; + + public Fields() {} @Function public Fields withVersion(String version) { this.version = version; + this.internalVersion = version; + this.publicVersion = version; return this; } @@ -22,4 +26,9 @@ public Fields withVersion(String version) { public String getVersion() { return version; } + + @Function + public String getInternalVersion() { + return internalVersion; + } } diff --git a/core/integration/testdata/nested-c2c/main.go b/core/integration/testdata/nested-c2c/main.go index 4f376eab78a..0bca40ca2d3 100644 --- a/core/integration/testdata/nested-c2c/main.go +++ b/core/integration/testdata/nested-c2c/main.go @@ -85,7 +85,7 @@ func weHaveToGoDeeper(ctx context.Context, c *dagger.Client, depth int, mode str args = append(args, mirrorURL) out, err := c.Container(). - From("golang:1.24.0-alpine"). + From("golang:1.23.6-alpine"). WithMountedCache("/go/pkg/mod", c.CacheVolume("go-mod")). WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). WithMountedCache("/go/build-cache", c.CacheVolume("go-build")). diff --git a/core/modules/config.go b/core/modules/config.go index 469eacc0f04..9b2f8dc48a9 100644 --- a/core/modules/config.go +++ b/core/modules/config.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/dagger/dagger/engine" + "github.com/vektah/gqlparser/v2/ast" ) // Filename is the name of the module config file. @@ -70,6 +71,9 @@ type ModuleConfig struct { // Paths to explicitly exclude from the module, relative to the configuration file. // Deprecated: Use ! in the include list instead. Exclude []string `json:"exclude,omitempty"` + + // The clients generated for this module. + Clients []*ModuleConfigClient `json:"clients,omitempty"` } type ModuleConfigUserFields struct { @@ -233,3 +237,32 @@ func (cfg ModuleCodegenConfig) Clone() *ModuleCodegenConfig { cfg.AutomaticGitignore = &clone return &cfg } + +type ModuleConfigClient struct { + // The generator the client uses to be generated. + Generator string `field:"true" name:"generator" json:"generator" doc:"The generator to use"` + + // The directory the client is generated in. + Directory string `field:"true" name:"directory" json:"directory" doc:"The directory the client is generated in."` + + // Whether the client is generated in Dev mode or not. + // If set using an official SDK like Go or Typescript, the client will use the local SDK library + // instead of the published one. + Dev *bool `field:"true" name:"dev" json:"localLibrary,omitempty" doc:"If true, generate the client in developer mode."` +} + +func (*ModuleConfigClient) Type() *ast.Type { + return &ast.Type{ + NamedType: "ModuleConfigClient", + NonNull: true, + } +} + +func (*ModuleConfigClient) TypeDescription() string { + return "The client generated for the module." +} + +func (m ModuleConfigClient) Clone() *ModuleConfigClient { + cp := m + return &cp +} diff --git a/core/modulesource.go b/core/modulesource.go index 2ade3a25864..407dec8c17d 100644 --- a/core/modulesource.go +++ b/core/modulesource.go @@ -107,6 +107,9 @@ type ModuleSource struct { // Dependencies are the loaded sources for the module's dependencies Dependencies []dagql.Instance[*ModuleSource] `field:"true" name:"dependencies" doc:"The dependencies of the module source."` + // Clients are the clients generated for the module. + ConfigClients []*modules.ModuleConfigClient `field:"true" name:"configClients" doc:"The clients generated for the module."` + // SourceRootSubpath is the relative path from the context dir to the dir containing the module's dagger.json SourceRootSubpath string `field:"true" name:"sourceRootSubpath" doc:"The path, relative to the context directory, that contains the module's dagger.json."` // SourceSubpath is the relative path from the context dir to the dir containing the module's source code @@ -176,6 +179,10 @@ func (src ModuleSource) Clone() *ModuleSource { src.Git = src.Git.Clone() } + oriConfigClients := src.ConfigClients + src.ConfigClients = make([]*modules.ModuleConfigClient, len(oriConfigClients)) + copy(src.ConfigClients, oriConfigClients) + return &src } @@ -269,6 +276,13 @@ func (src *ModuleSource) CalcDigest() digest.Digest { inputs = append(inputs, dep.Self.Digest) } + for _, client := range src.ConfigClients { + inputs = append(inputs, client.Generator, client.Directory) + if client.Dev != nil { + inputs = append(inputs, fmt.Sprintf("%t", *client.Dev)) + } + } + return dagql.HashFrom(inputs...) } diff --git a/core/schema/git.go b/core/schema/git.go index 48e6000f553..f019e026121 100644 --- a/core/schema/git.go +++ b/core/schema/git.go @@ -8,7 +8,6 @@ import ( "encoding/hex" "errors" "fmt" - "log/slog" "os" "os/exec" "regexp" @@ -18,6 +17,7 @@ import ( "github.com/dagger/dagger/dagql" "github.com/dagger/dagger/engine" "github.com/dagger/dagger/engine/server/resource" + "github.com/dagger/dagger/engine/slog" "github.com/dagger/dagger/engine/sources/gitdns" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" @@ -194,12 +194,12 @@ func (s *gitSchema) git(ctx context.Context, parent dagql.Instance[*core.Query], // For HTTP(S) refs, handle PAT auth if we're the main client if (remote.Scheme == "https" || remote.Scheme == "http") && clientMetadata != nil { - mainClientCallerMetadata, err := parent.Self.NonModuleParentClientMetadata(ctx) + parentClientMetadata, err := parent.Self.NonModuleParentClientMetadata(ctx) if err != nil { - return inst, fmt.Errorf("failed to retrieve mainClientCallerID: %w", err) + return inst, fmt.Errorf("failed to retrieve non-module parent client metadata: %w", err) } - if clientMetadata.ClientID == mainClientCallerMetadata.ClientID { + if clientMetadata.ClientID == parentClientMetadata.ClientID { // Check if repo is public repo := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{ Name: "origin", @@ -207,9 +207,13 @@ func (s *gitSchema) git(ctx context.Context, parent dagql.Instance[*core.Query], }) _, err := repo.ListContext(ctx, &git.ListOptions{Auth: nil}) - if err != nil && errors.Is(err, transport.ErrAuthenticationRequired) { + switch { + case err == nil: + // no auth needed + + case errors.Is(err, transport.ErrAuthenticationRequired): // Only proceed with auth if repo requires authentication - authCtx := engine.ContextWithClientMetadata(ctx, mainClientCallerMetadata) + authCtx := engine.ContextWithClientMetadata(ctx, parentClientMetadata) bk, err := parent.Self.Buildkit(authCtx) if err != nil { @@ -218,7 +222,14 @@ func (s *gitSchema) git(ctx context.Context, parent dagql.Instance[*core.Query], // Retrieve credential from host credentials, err := bk.GetCredential(authCtx, remote.Scheme, remote.Host, remote.Path) - if err == nil { + switch { + case err != nil: + slog.Warn("failed to retrieve git credentials, continuing without authentication", "error", err, "url", args.URL) + + case credentials == nil || credentials.Password == "": + slog.Warn("no credentials found, continuing without authentication", "url", args.URL) + + default: // Credentials found, create and set auth token hash := sha256.Sum256([]byte(credentials.Password)) secretName := hex.EncodeToString(hash[:]) @@ -241,9 +252,10 @@ func (s *gitSchema) git(ctx context.Context, parent dagql.Instance[*core.Query], return inst, fmt.Errorf("failed to create a new secret with the git auth token: %w", err) } authToken = secretAuthToken - } else { - slog.Warn(fmt.Sprintf("failed to retrieve git credentials, continuing without authentication: %s", err.Error())) } + + default: + slog.Warn("failed to list remote refs, continuing without authentication", "error", err, "url", args.URL) } } } diff --git a/core/schema/modulesource.go b/core/schema/modulesource.go index a3042759e71..de7fdc9979f 100644 --- a/core/schema/modulesource.go +++ b/core/schema/modulesource.go @@ -19,6 +19,7 @@ import ( "github.com/dagger/dagger/dagql" "github.com/dagger/dagger/engine" "github.com/dagger/dagger/engine/buildkit" + "github.com/dagger/dagger/engine/cache" "github.com/dagger/dagger/engine/client/pathutil" "github.com/dagger/dagger/engine/server/resource" "github.com/opencontainers/go-digest" @@ -143,14 +144,15 @@ func (s *moduleSourceSchema) Install() { Doc(`The URL to clone the root of the git repo from`). Deprecated("Use `cloneRef` instead. `cloneRef` supports both URL-style and SCP-like SSH references"), - dagql.NodeFunc("generateClient", s.moduleSourceGenerateClient). - Doc(`Generates a client for the module.`). + dagql.Func("withClient", s.moduleSourceWithClient). + Doc(`Update the module source with a new client to generate.`). ArgDoc("generator", `The generator to use`). ArgDoc("outputDir", `The output directory for the generated client.`). - ArgDoc("localSdk", `Use local SDK dependency`), + ArgDoc("dev", `Generate in developer mode`), }.Install(s.dag) dagql.Fields[*core.SDKConfig]{}.Install(s.dag) + dagql.Fields[*modules.ModuleConfigClient]{}.Install(s.dag) dagql.Fields[*core.GeneratedCode]{ dagql.Func("withVCSGeneratedPaths", s.generatedCodeWithVCSGeneratedPaths). @@ -745,6 +747,7 @@ func (s *moduleSourceSchema) initFromModConfig(configBytes []byte, src *core.Mod src.CodegenConfig = modCfg.Codegen src.ModuleConfigUserFields = modCfg.ModuleConfigUserFields src.ConfigDependencies = modCfg.Dependencies + src.ConfigClients = modCfg.Clients engineVersion := modCfg.EngineVersion switch engineVersion { @@ -936,7 +939,7 @@ func resolveDepToSource( } err = dag.Select(ctx, dag.Root(), &inst, selectors...) if err != nil { - if errors.Is(err, dagql.ErrCacheMapRecursiveCall) { + if errors.Is(err, cache.ErrCacheRecursiveCall) { return inst, fmt.Errorf("module %q has a circular dependency on itself through dependency %q", parentSrc.ModuleName, depName) } return inst, fmt.Errorf("failed to load local dep: %w", err) @@ -1701,14 +1704,9 @@ func (s *moduleSourceSchema) moduleSourceWithoutDependencies( return parentSrc, nil } -//nolint:gocyclo -func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( - ctx context.Context, - srcInst dagql.Instance[*core.ModuleSource], - args struct{}, -) (genDirInst dagql.Instance[*core.Directory], err error) { - src := srcInst.Self - +func (s *moduleSourceSchema) loadModuleSourceConfig( + src *core.ModuleSource, +) (*modules.ModuleConfigWithUserFields, error) { // construct the module config based on any config read during load and any settings changed via with* APIs modCfg := &modules.ModuleConfigWithUserFields{ ModuleConfigUserFields: src.ModuleConfigUserFields, @@ -1717,6 +1715,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( EngineVersion: src.EngineVersion, Include: src.IncludePaths, Codegen: src.CodegenConfig, + Clients: src.ConfigClients, }, } @@ -1726,14 +1725,16 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( } } + // Check version compatibility. if !engine.CheckVersionCompatibility(modCfg.EngineVersion, engine.MinimumModuleVersion) { - return genDirInst, fmt.Errorf("module requires dagger %s, but support for that version has been removed", modCfg.EngineVersion) + return nil, fmt.Errorf("module requires dagger %s, but support for that version has been removed", modCfg.EngineVersion) } if !engine.CheckMaxVersionCompatibility(modCfg.EngineVersion, engine.BaseVersion(engine.Version)) { - return genDirInst, fmt.Errorf("module requires dagger %s, but you have %s", modCfg.EngineVersion, engine.Version) + return nil, fmt.Errorf("module requires dagger %s, but you have %s", modCfg.EngineVersion, engine.Version) } - switch srcInst.Self.SourceSubpath { + // Load the module config source based on sourcePath. + switch src.SourceSubpath { case "": // leave unset default: @@ -1743,7 +1744,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( filepath.Join("/", src.SourceSubpath), ) if err != nil { - return genDirInst, fmt.Errorf("failed to get relative path from source root to source: %w", err) + return nil, fmt.Errorf("failed to get relative path from source root to source: %w", err) } // if source is ".", leave it unset in dagger.json as that's the default if modCfg.Source == "." { @@ -1751,6 +1752,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( } } + // Load configuration for each dependencies. modCfg.Dependencies = make([]*modules.ModuleConfigDependency, len(src.Dependencies)) for i, depSrc := range src.Dependencies { depCfg := &modules.ModuleConfigDependency{ @@ -1758,7 +1760,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( } modCfg.Dependencies[i] = depCfg - switch srcInst.Self.Kind { + switch src.Kind { case core.ModuleSourceKindLocal: switch depSrc.Self.Kind { case core.ModuleSourceKindLocal: @@ -1767,7 +1769,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( depSrcRoot := filepath.Join(depSrc.Self.Local.ContextDirectoryPath, depSrc.Self.SourceRootSubpath) depSrcRoot, err := pathutil.LexicalRelativePath(parentSrcRoot, depSrcRoot) if err != nil { - return genDirInst, fmt.Errorf("failed to get relative path: %w", err) + return nil, fmt.Errorf("failed to get relative path: %w", err) } depCfg.Source = depSrcRoot @@ -1777,24 +1779,24 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( depCfg.Pin = depSrc.Self.Git.Pin default: - return genDirInst, fmt.Errorf("unhandled module source kind: %s", srcInst.Self.Kind.HumanString()) + return nil, fmt.Errorf("unhandled module source kind: %s", src.Kind.HumanString()) } case core.ModuleSourceKindGit: switch depSrc.Self.Kind { case core.ModuleSourceKindLocal: // parent=git, dep=local - return genDirInst, fmt.Errorf("cannot add local module source as dependency of git module source") + return nil, fmt.Errorf("cannot add local module source as dependency of git module source") case core.ModuleSourceKindGit: // parent=git, dep=git // check if the dep is the same git repo + pin as the parent, if so make it a local dep - if srcInst.Self.Git.CloneRef == depSrc.Self.Git.CloneRef && srcInst.Self.Git.Pin == depSrc.Self.Git.Pin { + if src.Git.CloneRef == depSrc.Self.Git.CloneRef && src.Git.Pin == depSrc.Self.Git.Pin { parentSrcRoot := filepath.Join("/", src.SourceRootSubpath) depSrcRoot := filepath.Join("/", depSrc.Self.SourceRootSubpath) depSrcRoot, err := pathutil.LexicalRelativePath(parentSrcRoot, depSrcRoot) if err != nil { - return genDirInst, fmt.Errorf("failed to get relative path: %w", err) + return nil, fmt.Errorf("failed to get relative path: %w", err) } depCfg.Source = depSrcRoot } else { @@ -1803,7 +1805,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( } default: - return genDirInst, fmt.Errorf("unhandled module source kind: %s", srcInst.Self.Kind.HumanString()) + return nil, fmt.Errorf("unhandled module source kind: %s", src.Kind.HumanString()) } case core.ModuleSourceKindDir: @@ -1818,7 +1820,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( depSrcRoot := filepath.Join("/", depSrc.Self.SourceRootSubpath) depSrcRoot, err := pathutil.LexicalRelativePath(parentSrcRoot, depSrcRoot) if err != nil { - return genDirInst, fmt.Errorf("failed to get relative path: %w", err) + return nil, fmt.Errorf("failed to get relative path: %w", err) } depCfg.Source = depSrcRoot @@ -1830,127 +1832,258 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( default: // Local not supported since there's nothing we could plausibly put in the dagger.json for // a Dir-kind module source to depend on a Local-kind module source - return genDirInst, fmt.Errorf("parent module source kind %s cannot have dependency of kind %s", - srcInst.Self.Kind.HumanString(), + return nil, fmt.Errorf("parent module source kind %s cannot have dependency of kind %s", + src.Kind.HumanString(), depSrc.Self.Kind.HumanString(), ) } default: - return genDirInst, fmt.Errorf("unhandled module source kind: %s", srcInst.Self.Kind.HumanString()) + return nil, fmt.Errorf("unhandled module source kind: %s", src.Kind.HumanString()) } } - // run codegen too if we have a name and SDK - genDirInst = src.ContextDirectory - if modCfg.Name != "" && modCfg.SDK != nil && modCfg.SDK.Source != "" { - // load the deps as actual Modules - deps, err := s.loadDependencyModules(ctx, srcInst.Self) - if err != nil { - return genDirInst, fmt.Errorf("failed to load dependencies as modules: %w", err) - } + return modCfg, nil +} - // cache the current source instance by it's digest before passing to codegen - // this scopes the cache key of codegen calls to an exact content hash detached - // from irrelevant details like specific host paths, specific git repos+commits, etc. - _, _, err = s.dag.Cache.GetOrInitializeValue(ctx, digest.Digest(srcInst.Self.Digest), srcInst) - if err != nil { - return genDirInst, fmt.Errorf("failed to get or initialize instance: %w", err) +func (s *moduleSourceSchema) runCodegen( + ctx context.Context, + srcInst dagql.Instance[*core.ModuleSource], + genDirInst dagql.Instance[*core.Directory], +) (dagql.Instance[*core.Directory], error) { + // load the deps as actual Modules + deps, err := s.loadDependencyModules(ctx, srcInst.Self) + if err != nil { + return genDirInst, fmt.Errorf("failed to load dependencies as modules: %w", err) + } + + // cache the current source instance by it's digest before passing to codegen + // this scopes the cache key of codegen calls to an exact content hash detached + // from irrelevant details like specific host paths, specific git repos+commits, etc. + _, err = s.dag.Cache.GetOrInitializeValue(ctx, digest.Digest(srcInst.Self.Digest), srcInst) + if err != nil { + return genDirInst, fmt.Errorf("failed to get or initialize instance: %w", err) + } + srcInstContentHashed := srcInst.WithDigest(digest.Digest(srcInst.Self.Digest)) + + // run codegen to get the generated context directory + generatedCode, err := srcInst.Self.SDKImpl.Codegen(ctx, deps, srcInstContentHashed) + if err != nil { + return genDirInst, fmt.Errorf("failed to generate code: %w", err) + } + genDirInst = generatedCode.Code + + // update .gitattributes in the generated context directory + // (linter thinks this chunk of code is too similar to the below, but not clear abstraction is worth it) + //nolint:dupl + if len(generatedCode.VCSGeneratedPaths) > 0 { + gitAttrsPath := filepath.Join(srcInst.Self.SourceSubpath, ".gitattributes") + var gitAttrsContents []byte + gitAttrsFile, err := srcInst.Self.ContextDirectory.Self.File(ctx, gitAttrsPath) + if err == nil { + gitAttrsContents, err = gitAttrsFile.Contents(ctx) + if err != nil { + return genDirInst, fmt.Errorf("failed to get git attributes file contents: %w", err) + } + if !bytes.HasSuffix(gitAttrsContents, []byte("\n")) { + gitAttrsContents = append(gitAttrsContents, []byte("\n")...) + } + } + for _, fileName := range generatedCode.VCSGeneratedPaths { + if bytes.Contains(gitAttrsContents, []byte(fileName)) { + // already has some config for the file + continue + } + fileName := strings.TrimPrefix(fileName, "/") + gitAttrsContents = append(gitAttrsContents, + []byte(fmt.Sprintf("/%s linguist-generated\n", fileName))..., + ) } - srcInstContentHashed := srcInst.WithDigest(digest.Digest(srcInst.Self.Digest)) - // run codegen to get the generated context directory - generatedCode, err := srcInst.Self.SDKImpl.Codegen(ctx, deps, srcInstContentHashed) + err = s.dag.Select(ctx, genDirInst, &genDirInst, + dagql.Selector{ + Field: "withNewFile", + Args: []dagql.NamedInput{ + {Name: "path", Value: dagql.String(gitAttrsPath)}, + {Name: "contents", Value: dagql.String(gitAttrsContents)}, + {Name: "permissions", Value: dagql.Int(0o600)}, + }, + }, + ) if err != nil { - return genDirInst, fmt.Errorf("failed to generate code: %w", err) + return genDirInst, fmt.Errorf("failed to add vcs generated file: %w", err) } - genDirInst = generatedCode.Code + } - // update .gitattributes in the generated context directory - // (linter thinks this chunk of code is too similar to the below, but not clear abstraction is worth it) - //nolint:dupl - if len(generatedCode.VCSGeneratedPaths) > 0 { - gitAttrsPath := filepath.Join(srcInst.Self.SourceSubpath, ".gitattributes") - var gitAttrsContents []byte - gitAttrsFile, err := srcInst.Self.ContextDirectory.Self.File(ctx, gitAttrsPath) - if err == nil { - gitAttrsContents, err = gitAttrsFile.Contents(ctx) - if err != nil { - return genDirInst, fmt.Errorf("failed to get git attributes file contents: %w", err) - } - if !bytes.HasSuffix(gitAttrsContents, []byte("\n")) { - gitAttrsContents = append(gitAttrsContents, []byte("\n")...) - } + // update .gitignore in the generated context directory + writeGitignore := true // default to true if not set + if srcInst.Self.CodegenConfig != nil && srcInst.Self.CodegenConfig.AutomaticGitignore != nil { + writeGitignore = *srcInst.Self.CodegenConfig.AutomaticGitignore + } + // (linter thinks this chunk of code is too similar to the above, but not clear abstraction is worth it) + //nolint:dupl + if writeGitignore && len(generatedCode.VCSIgnoredPaths) > 0 { + gitIgnorePath := filepath.Join(srcInst.Self.SourceSubpath, ".gitignore") + var gitIgnoreContents []byte + gitIgnoreFile, err := srcInst.Self.ContextDirectory.Self.File(ctx, gitIgnorePath) + if err == nil { + gitIgnoreContents, err = gitIgnoreFile.Contents(ctx) + if err != nil { + return genDirInst, fmt.Errorf("failed to get .gitignore file contents: %w", err) } - for _, fileName := range generatedCode.VCSGeneratedPaths { - if bytes.Contains(gitAttrsContents, []byte(fileName)) { - // already has some config for the file - continue - } - fileName := strings.TrimPrefix(fileName, "/") - gitAttrsContents = append(gitAttrsContents, - []byte(fmt.Sprintf("/%s linguist-generated\n", fileName))..., - ) + if !bytes.HasSuffix(gitIgnoreContents, []byte("\n")) { + gitIgnoreContents = append(gitIgnoreContents, []byte("\n")...) + } + } + for _, fileName := range generatedCode.VCSIgnoredPaths { + if bytes.Contains(gitIgnoreContents, []byte(fileName)) { + continue } + fileName := strings.TrimPrefix(fileName, "/") + gitIgnoreContents = append(gitIgnoreContents, + []byte(fmt.Sprintf("/%s\n", fileName))..., + ) + } - err = s.dag.Select(ctx, genDirInst, &genDirInst, - dagql.Selector{ - Field: "withNewFile", - Args: []dagql.NamedInput{ - {Name: "path", Value: dagql.String(gitAttrsPath)}, - {Name: "contents", Value: dagql.String(gitAttrsContents)}, - {Name: "permissions", Value: dagql.Int(0o600)}, - }, + err = s.dag.Select(ctx, genDirInst, &genDirInst, + dagql.Selector{ + Field: "withNewFile", + Args: []dagql.NamedInput{ + {Name: "path", Value: dagql.String(gitIgnorePath)}, + {Name: "contents", Value: dagql.String(gitIgnoreContents)}, + {Name: "permissions", Value: dagql.Int(0o600)}, }, - ) - if err != nil { - return genDirInst, fmt.Errorf("failed to add vcs generated file: %w", err) - } + }, + ) + if err != nil { + return genDirInst, fmt.Errorf("failed to add vcs ignore file: %w", err) } + } + + return genDirInst, nil +} + +func (s *moduleSourceSchema) runClientGenerator( + ctx context.Context, + srcInst dagql.Instance[*core.ModuleSource], + genDirInst dagql.Instance[*core.Directory], + clientGeneratorConfig *modules.ModuleConfigClient, +) (dagql.Instance[*core.Directory], error) { + src := srcInst.Self + + generator, err := newSDKLoader(s.dag).sdkForModule( + ctx, + src.Query, + &core.SDKConfig{ + Source: clientGeneratorConfig.Generator, + }, + src, + ) + if err != nil { + return genDirInst, fmt.Errorf("failed to load generator module %s: %w", clientGeneratorConfig.Generator, err) + } + + requiredClientGenerationFiles, err := generator.RequiredClientGenerationFiles(ctx) + if err != nil { + return genDirInst, fmt.Errorf("failed to get required client generation files: %w", err) + } + + // Add extra files required to correctly generate the client + var source dagql.Instance[*core.ModuleSource] + err = s.dag.Select(ctx, srcInst, &source, dagql.Selector{ + Field: "withIncludes", + Args: []dagql.NamedInput{ + { + Name: "patterns", + Value: dagql.ArrayInput[dagql.String](requiredClientGenerationFiles), + }, + }, + }) + if err != nil { + return genDirInst, fmt.Errorf("failed to add module source required files: %w", err) + } + + deps, err := s.loadDependencyModules(ctx, srcInst.Self) + if err != nil { + return genDirInst, fmt.Errorf("failed to load dependencies of this modules: %w", err) + } - // update .gitignore in the generated context directory - writeGitignore := true // default to true if not set - if srcInst.Self.CodegenConfig != nil && srcInst.Self.CodegenConfig.AutomaticGitignore != nil { - writeGitignore = *srcInst.Self.CodegenConfig.AutomaticGitignore + // If the current module source has sources, we can transform it into a module + // to generate self bindings. + if srcInst.Self.SDK != nil { + var mod dagql.Instance[*core.Module] + err = s.dag.Select(ctx, srcInst, &mod, dagql.Selector{ + Field: "asModule", + }) + if err != nil { + return genDirInst, fmt.Errorf("failed to transform module source into module: %w", err) } - // (linter thinks this chunk of code is too similar to the above, but not clear abstraction is worth it) - //nolint:dupl - if writeGitignore && len(generatedCode.VCSIgnoredPaths) > 0 { - gitIgnorePath := filepath.Join(srcInst.Self.SourceSubpath, ".gitignore") - var gitIgnoreContents []byte - gitIgnoreFile, err := srcInst.Self.ContextDirectory.Self.File(ctx, gitIgnorePath) - if err == nil { - gitIgnoreContents, err = gitIgnoreFile.Contents(ctx) - if err != nil { - return genDirInst, fmt.Errorf("failed to get .gitignore file contents: %w", err) - } - if !bytes.HasSuffix(gitIgnoreContents, []byte("\n")) { - gitIgnoreContents = append(gitIgnoreContents, []byte("\n")...) - } - } - for _, fileName := range generatedCode.VCSIgnoredPaths { - if bytes.Contains(gitIgnoreContents, []byte(fileName)) { - continue - } - fileName := strings.TrimPrefix(fileName, "/") - gitIgnoreContents = append(gitIgnoreContents, - []byte(fmt.Sprintf("/%s\n", fileName))..., - ) - } - err = s.dag.Select(ctx, genDirInst, &genDirInst, - dagql.Selector{ - Field: "withNewFile", - Args: []dagql.NamedInput{ - {Name: "path", Value: dagql.String(gitIgnorePath)}, - {Name: "contents", Value: dagql.String(gitIgnoreContents)}, - {Name: "permissions", Value: dagql.Int(0o600)}, - }, + deps = mod.Self.Deps.Append(mod.Self) + } + + dev := dagql.Boolean(false) + if clientGeneratorConfig.Dev != nil { + dev = dagql.Boolean(*clientGeneratorConfig.Dev) + } + + generatedClientDir, err := generator.GenerateClient( + ctx, + source, + deps, + clientGeneratorConfig.Directory, + dev.Bool(), + ) + if err != nil { + return genDirInst, fmt.Errorf("failed to generate clients: %w", err) + } + + // Merge the generated client to the current generated instance + err = s.dag.Select(ctx, genDirInst, &genDirInst, + dagql.Selector{ + Field: "withDirectory", + Args: []dagql.NamedInput{ + { + Name: "path", + Value: dagql.String("/"), }, - ) - if err != nil { - return genDirInst, fmt.Errorf("failed to add vcs ignore file: %w", err) - } + { + Name: "directory", + Value: dagql.NewID[*core.Directory](generatedClientDir.ID()), + }, + }, + }) + if err != nil { + return genDirInst, fmt.Errorf("failed to add client generated to generated directory: %w", err) + } + + return genDirInst, nil +} + +func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( + ctx context.Context, + srcInst dagql.Instance[*core.ModuleSource], + args struct{}, +) (genDirInst dagql.Instance[*core.Directory], err error) { + modCfg, err := s.loadModuleSourceConfig(srcInst.Self) + if err != nil { + return genDirInst, fmt.Errorf("failed to load module source config: %w", err) + } + + // run codegen too if we have a name and SDK + genDirInst = srcInst.Self.ContextDirectory + if modCfg.Name != "" && modCfg.SDK != nil && modCfg.SDK.Source != "" { + genDirInst, err = s.runCodegen(ctx, srcInst, genDirInst) + if err != nil { + return genDirInst, fmt.Errorf("failed to run codegen: %w", err) + } + } + + // Generate clients + for _, client := range modCfg.Clients { + genDirInst, err = s.runClientGenerator(ctx, srcInst, genDirInst, client) + if err != nil { + return genDirInst, fmt.Errorf("failed to run client generator %s: %w", client.Generator, err) } } @@ -1960,7 +2093,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( return genDirInst, fmt.Errorf("failed to encode module config: %w", err) } modCfgBytes = append(modCfgBytes, '\n') - modCfgPath := filepath.Join(src.SourceRootSubpath, modules.Filename) + modCfgPath := filepath.Join(srcInst.Self.SourceRootSubpath, modules.Filename) err = s.dag.Select(ctx, genDirInst, &genDirInst, dagql.Selector{ Field: "withNewFile", @@ -1976,7 +2109,7 @@ func (s *moduleSourceSchema) moduleSourceGeneratedContextDirectory( } // return just the diff of what we generated relative to the original context directory - err = s.dag.Select(ctx, src.ContextDirectory, &genDirInst, + err = s.dag.Select(ctx, srcInst.Self.ContextDirectory, &genDirInst, dagql.Selector{ Field: "diff", Args: []dagql.NamedInput{ @@ -2029,7 +2162,7 @@ func (s *moduleSourceSchema) moduleSourceAsModule( // cache the current source instance by it's digest before passing to codegen // this scopes the cache key of codegen calls to an exact content hash detached // from irrelevant details like specific host paths, specific git repos+commits, etc. - _, _, err = s.dag.Cache.GetOrInitializeValue(ctx, digest.Digest(src.Self.Digest), src) + _, err = s.dag.Cache.GetOrInitializeValue(ctx, digest.Digest(src.Self.Digest), src) if err != nil { return inst, fmt.Errorf("failed to get or initialize instance: %w", err) } @@ -2163,78 +2296,36 @@ func (s *moduleSourceSchema) loadDependencyModules(ctx context.Context, src *cor return deps, nil } -func (s *moduleSourceSchema) moduleSourceGenerateClient( +func (s *moduleSourceSchema) moduleSourceWithClient( ctx context.Context, - src dagql.Instance[*core.ModuleSource], + src *core.ModuleSource, args struct { Generator dagql.String OutputDir dagql.String - LocalSdk dagql.Optional[dagql.Boolean] + Dev dagql.Optional[dagql.Boolean] }, -) (*core.Directory, error) { - generator, err := newSDKLoader(s.dag).sdkForModule( - ctx, - src.Self.Query, - &core.SDKConfig{ - Source: args.Generator.String(), - }, - src.Self, - ) - if err != nil { - return nil, fmt.Errorf("failed to load generator module %s: %w", args.Generator, err) - } +) (*core.ModuleSource, error) { + src = src.Clone() - requiredClientGenerationFiles, err := generator.RequiredClientGenerationFiles(ctx) - if err != nil { - return nil, fmt.Errorf("failed to get required client generation files: %w", err) + if src.ConfigClients == nil { + src.ConfigClients = []*modules.ModuleConfigClient{} } - // Add extra files required to correctly generate the client - var source dagql.Instance[*core.ModuleSource] - err = s.dag.Select(ctx, src, &source, dagql.Selector{ - Field: "withIncludes", - Args: []dagql.NamedInput{ - { - Name: "patterns", - Value: dagql.ArrayInput[dagql.String](requiredClientGenerationFiles), - }, - }, - }) - if err != nil { - return nil, fmt.Errorf("failed to add module source required files: %w", err) + moduleConfigClient := &modules.ModuleConfigClient{ + Generator: args.Generator.String(), + Directory: args.OutputDir.String(), } - deps, err := s.loadDependencyModules(ctx, src.Self) - if err != nil { - return nil, fmt.Errorf("failed to load dependencies of this modules: %w", err) + if args.Dev.Valid { + value := args.Dev.Value.Bool() + moduleConfigClient.Dev = &value } - // If the current module source has sources, we can transform it into a module - // to generate self bindings. - if src.Self.SDK != nil { - var mod dagql.Instance[*core.Module] - err = s.dag.Select(ctx, src, &mod, dagql.Selector{ - Field: "asModule", - }) - if err != nil { - return nil, fmt.Errorf("failed to transform module source into module: %w", err) - } - - deps = mod.Self.Deps.Append(mod.Self) - } + src.ConfigClients = append(src.ConfigClients, moduleConfigClient) - generatedClientDir, err := generator.GenerateClient( - ctx, - source, - deps, - args.OutputDir.String(), - args.LocalSdk.GetOr(false).Bool(), - ) - if err != nil { - return nil, fmt.Errorf("failed to generate clients: %w", err) - } + src.Digest = src.CalcDigest().String() - return generatedClientDir.Self, nil + return src, nil } // find-up a given soughtName in curDirPath and its parent directories, return the dir diff --git a/core/schema/sdk.go b/core/schema/sdk.go index 095cf983c16..cb04bbad405 100644 --- a/core/schema/sdk.go +++ b/core/schema/sdk.go @@ -248,38 +248,71 @@ func (sdk *moduleSDK) RequiredClientGenerationFiles( return res, nil } +// Return true and the function typedef if the function is implemented by the SDK module. +func (sdk *moduleSDK) isFunctionImplemented(name string) (*core.Function, bool) { + for _, def := range sdk.mod.Self.ObjectDefs { + if !def.AsObject.Valid { + continue + } + + obj := def.AsObject.Value + if gqlFieldName(obj.Name) != gqlFieldName(sdk.mod.Self.OriginalName) { + continue + } + + for _, fn := range obj.Functions { + if fn.Name == name { + return fn, true + } + } + } + + return nil, false +} + func (sdk *moduleSDK) GenerateClient( ctx context.Context, modSource dagql.Instance[*core.ModuleSource], deps *core.ModDeps, outputDir string, - useLocalSDK bool, + dev bool, ) (inst dagql.Instance[*core.Directory], err error) { schemaJSONFile, err := deps.SchemaIntrospectionJSONFile(ctx) if err != nil { return inst, fmt.Errorf("failed to get schema introspection json during module client generation: %w", err) } + fct, implements := sdk.isFunctionImplemented("generateClient") + if !implements { + return inst, fmt.Errorf("generateClient is not implemented by this SDK") + } + + generateClientsArgs := []dagql.NamedInput{ + { + Name: "modSource", + Value: dagql.NewID[*core.ModuleSource](modSource.ID()), + }, + { + Name: "introspectionJson", + Value: dagql.NewID[*core.File](schemaJSONFile.ID()), + }, + { + Name: "outputDir", + Value: dagql.String(outputDir), + }, + } + + _, devFlagExist := fct.LookupArg("dev") + if devFlagExist { + generateClientsArgs = append(generateClientsArgs, dagql.NamedInput{ + Name: "dev", + Value: dagql.NewBoolean(dev), + }) + } + err = sdk.dag.Select(ctx, sdk.sdk, &inst, dagql.Selector{ Field: "generateClient", - Args: []dagql.NamedInput{ - { - Name: "modSource", - Value: dagql.NewID[*core.ModuleSource](modSource.ID()), - }, - { - Name: "introspectionJson", - Value: dagql.NewID[*core.File](schemaJSONFile.ID()), - }, - { - Name: "outputDir", - Value: dagql.String(outputDir), - }, - { - Name: "useLocalSdk", - Value: dagql.NewBoolean(useLocalSDK), - }, - }, + Args: generateClientsArgs, }) if err != nil { return inst, fmt.Errorf("failed to call sdk module generate client: %w", err) @@ -436,7 +469,7 @@ func (sdk *goSDK) GenerateClient( modSource dagql.Instance[*core.ModuleSource], deps *core.ModDeps, outputDir string, - useLocalSDK bool, + dev bool, ) (inst dagql.Instance[*core.Directory], err error) { schemaJSONFile, err := deps.SchemaIntrospectionJSONFile(ctx) if err != nil { @@ -448,33 +481,16 @@ func (sdk *goSDK) GenerateClient( return inst, fmt.Errorf("failed to get base container during module client generation: %w", err) } - workdirPath := "/module" + contextDir := modSource.Self.ContextDirectory + rootSourcePath := modSource.Self.SourceRootSubpath codegenArgs := dagql.ArrayInput[dagql.String]{ "--output", dagql.String(outputDir), "--introspection-json-path", goSDKIntrospectionJSONPath, - dagql.String(fmt.Sprintf("--local-sdk=%t", useLocalSDK)), + dagql.String(fmt.Sprintf("--dev=%t", dev)), "--client-only", } - var currentModuleDirectory dagql.Instance[*core.Directory] - err = sdk.dag.Select(ctx, modSource, ¤tModuleDirectory, - dagql.Selector{ - Field: "contextDirectory", - }, - dagql.Selector{ - Field: "directory", - Args: []dagql.NamedInput{ - { - Name: "path", - Value: dagql.String(modSource.Self.SourceRootSubpath), - }, - }, - }) - if err != nil { - return inst, fmt.Errorf("failed to get module source root directory: %w", err) - } - err = sdk.dag.Select(ctx, ctr, &ctr, dagql.Selector{ Field: "withMountedFile", @@ -493,15 +509,15 @@ func (sdk *goSDK) GenerateClient( Field: "withoutDefaultArgs", }, dagql.Selector{ - Field: "withDirectory", + Field: "withMountedDirectory", Args: []dagql.NamedInput{ { Name: "path", - Value: dagql.String(workdirPath), + Value: dagql.String(goSDKUserModContextDirPath), }, { - Name: "directory", - Value: dagql.NewID[*core.Directory](currentModuleDirectory.ID()), + Name: "source", + Value: dagql.NewID[*core.Directory](contextDir.ID()), }, }, }, @@ -510,7 +526,7 @@ func (sdk *goSDK) GenerateClient( Args: []dagql.NamedInput{ { Name: "path", - Value: dagql.String(workdirPath), + Value: dagql.NewString(filepath.Join(goSDKUserModContextDirPath, rootSourcePath)), }, }, }, @@ -536,7 +552,7 @@ func (sdk *goSDK) GenerateClient( Args: []dagql.NamedInput{ { Name: "path", - Value: dagql.String(workdirPath), + Value: dagql.String(goSDKUserModContextDirPath), }, }, }); err != nil { diff --git a/core/schema/wrapper.go b/core/schema/wrapper.go index 19f7b81e4a5..7d2674a97e0 100644 --- a/core/schema/wrapper.go +++ b/core/schema/wrapper.go @@ -10,7 +10,7 @@ import ( ) // DagOpWrapper caches an arbitrary dagql field as a buildkit operation -func DagOpWrapper[T dagql.Typed, A any, R dagql.Typed](srv *dagql.Server, fn dagql.NodeFuncHandler[T, A, R]) dagql.NodeFuncHandler[T, A, R] { +func DagOpWrapper[T dagql.Typed, A any, R dagql.Typed](srv *dagql.Server, fn func(ctx context.Context, self dagql.Instance[T], args A) (R, error)) func(ctx context.Context, self dagql.Instance[T], args A) (R, error) { return func(ctx context.Context, self dagql.Instance[T], args A) (inst R, err error) { if _, ok := core.DagOpFromContext[core.RawDagOp](ctx); ok { return fn(ctx, self, args) @@ -27,7 +27,9 @@ func DagOpWrapper[T dagql.Typed, A any, R dagql.Typed](srv *dagql.Server, fn dag // DagOpFileWrapper caches a file field as a buildkit operation - this is // more specialized than DagOpWrapper, since that serializes the value to // JSON, so we'd just end up with a cached ID instead of the actual content. -func DagOpFileWrapper[T dagql.Typed, A any](srv *dagql.Server, fn dagql.NodeFuncHandler[T, A, dagql.Instance[*core.File]]) dagql.NodeFuncHandler[T, A, dagql.Instance[*core.File]] { +// +// func DagOpFileWrapper[T dagql.Typed, A any](srv *dagql.Server, fn dagql.NodeFuncHandler[T, A, dagql.Instance[*core.File]]) dagql.NodeFuncHandler[T, A, dagql.Instance[*core.File]] { +func DagOpFileWrapper[T dagql.Typed, A any](srv *dagql.Server, fn func(ctx context.Context, self dagql.Instance[T], args A) (dagql.Instance[*core.File], error)) func(ctx context.Context, self dagql.Instance[T], args A) (dagql.Instance[*core.File], error) { return func(ctx context.Context, self dagql.Instance[T], args A) (inst dagql.Instance[*core.File], err error) { if _, ok := core.DagOpFromContext[core.DirectoryDagOp](ctx); ok { return fn(ctx, self, args) @@ -75,7 +77,9 @@ func DagOpFileWrapper[T dagql.Typed, A any](srv *dagql.Server, fn dagql.NodeFunc // DagOpDirectoryWrapper caches a directory field as a buildkit operation, // similar to DagOpFileWrapper. -func DagOpDirectoryWrapper[T dagql.Typed, A any](srv *dagql.Server, fn dagql.NodeFuncHandler[T, A, dagql.Instance[*core.Directory]]) dagql.NodeFuncHandler[T, A, dagql.Instance[*core.Directory]] { +// +// func DagOpDirectoryWrapper[T dagql.Typed, A any](srv *dagql.Server, fn dagql.NodeFuncHandler[T, A, dagql.Instance[*core.Directory]]) dagql.NodeFuncHandler[T, A, dagql.Instance[*core.Directory]] { +func DagOpDirectoryWrapper[T dagql.Typed, A any](srv *dagql.Server, fn func(ctx context.Context, self dagql.Instance[T], args A) (dagql.Instance[*core.Directory], error)) func(ctx context.Context, self dagql.Instance[T], args A) (dagql.Instance[*core.Directory], error) { return func(ctx context.Context, self dagql.Instance[T], args A) (inst dagql.Instance[*core.Directory], err error) { if _, ok := core.DagOpFromContext[core.DirectoryDagOp](ctx); ok { return fn(ctx, self, args) diff --git a/dagger.json b/dagger.json index 2af8cdb4485..20b6fa02724 100644 --- a/dagger.json +++ b/dagger.json @@ -1,6 +1,6 @@ { "name": "dagger-dev", - "engineVersion": "v0.16.1", + "engineVersion": "v0.16.2", "sdk": { "source": "go" }, @@ -46,6 +46,11 @@ "name": "helm", "source": "helm" }, + { + "name": "notify", + "source": "github.com/gerhard/daggerverse/notify", + "pin": "e4ee5778b10345d09a65a9655022d8ec40fc489f" + }, { "name": "php-sdk-dev", "source": "sdk/php/dev" diff --git a/dagql/cachemap.go b/dagql/cachemap.go deleted file mode 100644 index 7dcc25ae4ad..00000000000 --- a/dagql/cachemap.go +++ /dev/null @@ -1,146 +0,0 @@ -package dagql - -import ( - "context" - "fmt" - "sync" - - "github.com/opencontainers/go-digest" -) - -type CacheMap[K comparable, T any] interface { - GetOrInitialize(context.Context, K, func(context.Context) (T, error)) (T, bool, error) - Get(context.Context, K) (T, error) - Keys() []K -} - -type cacheMap[K comparable, T any] struct { - l sync.Mutex - calls map[K]*cache[T] -} - -type cache[T any] struct { - wg sync.WaitGroup - val T - err error - postCall func(context.Context) error -} - -// NewCache creates a new cache map suitable for assigning on a Server or -// multiple Servers. -func NewCache() Cache { - return newCacheMap[digest.Digest, Typed]() -} - -func NewCacheMap[K comparable, T any]() CacheMap[K, T] { - return newCacheMap[K, T]() -} - -func newCacheMap[K comparable, T any]() *cacheMap[K, T] { - return &cacheMap[K, T]{ - calls: map[K]*cache[T]{}, - } -} - -type cacheMapContextKey[K comparable, T any] struct { - key K - m *cacheMap[K, T] -} - -var ErrCacheMapRecursiveCall = fmt.Errorf("recursive call detected") - -func (m *cacheMap[K, T]) Set(key K, val T) { - m.l.Lock() - m.calls[key] = &cache[T]{ - val: val, - } - m.l.Unlock() -} - -func (m *cacheMap[K, T]) GetOrInitialize( - ctx context.Context, - key K, - fn func(ctx context.Context) (T, error), -) (T, bool, error) { - val, hit, _, err := m.GetOrInitializeWithPostCall(ctx, key, func(ctx context.Context) (T, func(context.Context) error, error) { - val, err := fn(ctx) - return val, nil, err - }) - return val, hit, err -} - -func (m *cacheMap[K, T]) GetOrInitializeWithPostCall( - ctx context.Context, - key K, - fn func(ctx context.Context) (T, func(context.Context) error, error), -) (T, bool, func(context.Context) error, error) { - var zeroKey K - if key == zeroKey { - // don't cache, don't dedupe calls, just call it - val, postCall, err := fn(ctx) - return val, false, postCall, err - } - - if v := ctx.Value(cacheMapContextKey[K, T]{key: key, m: m}); v != nil { - var zero T - return zero, false, nil, ErrCacheMapRecursiveCall - } - - m.l.Lock() - if c, ok := m.calls[key]; ok { - m.l.Unlock() - c.wg.Wait() - return c.val, true, c.postCall, c.err - } - - c := &cache[T]{} - c.wg.Add(1) - m.calls[key] = c - m.l.Unlock() - - ctx = context.WithValue(ctx, cacheMapContextKey[K, T]{key: key, m: m}, struct{}{}) - c.val, c.postCall, c.err = fn(ctx) - c.wg.Done() - - if c.err != nil { - m.l.Lock() - delete(m.calls, key) - m.l.Unlock() - } - - return c.val, false, c.postCall, c.err -} - -func (m *cacheMap[K, T]) GetOrInitializeValue(ctx context.Context, key K, v T) (T, bool, error) { - return m.GetOrInitialize(ctx, key, func(context.Context) (T, error) { - return v, nil - }) -} - -func (m *cacheMap[K, T]) Get(ctx context.Context, key K) (T, error) { - if v := ctx.Value(cacheMapContextKey[K, T]{key: key, m: m}); v != nil { - var zero T - return zero, ErrCacheMapRecursiveCall - } - - m.l.Lock() - if c, ok := m.calls[key]; ok { - m.l.Unlock() - c.wg.Wait() - return c.val, c.err - } - m.l.Unlock() - - var zero T - return zero, fmt.Errorf("key not found") -} - -func (m *cacheMap[K, T]) Keys() []K { - m.l.Lock() - keys := make([]K, 0, len(m.calls)) - for k := range m.calls { - keys = append(keys, k) - } - m.l.Unlock() - return keys -} diff --git a/dagql/cachemap_test.go b/dagql/cachemap_test.go deleted file mode 100644 index a81a7415b26..00000000000 --- a/dagql/cachemap_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package dagql - -import ( - "context" - "sync" - "testing" - - "github.com/pkg/errors" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" -) - -func TestCacheMapConcurrent(t *testing.T) { - t.Parallel() - c := newCacheMap[int, int]() - ctx := context.Background() - - commonKey := 42 - initialized := map[int]bool{} - - wg := new(sync.WaitGroup) - for i := 0; i < 100; i++ { - i := i - wg.Add(1) - go func() { - defer wg.Done() - val, _, err := c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { - initialized[i] = true - return i, nil - }) - assert.NilError(t, err) - assert.Assert(t, initialized[val]) - }() - } - - wg.Wait() - - // only one of them should have initialized - assert.Assert(t, is.Len(initialized, 1)) -} - -func TestCacheMapErrors(t *testing.T) { - t.Parallel() - c := newCacheMap[int, int]() - ctx := context.Background() - - commonKey := 42 - - myErr := errors.New("nope") - _, _, err := c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { - return 0, myErr - }) - assert.Assert(t, is.ErrorIs(err, myErr)) - - otherErr := errors.New("nope 2") - _, _, err = c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { - return 0, otherErr - }) - assert.Assert(t, is.ErrorIs(err, otherErr)) - - res, cached, err := c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { - return 1, nil - }) - assert.NilError(t, err) - assert.Assert(t, !cached) - assert.Equal(t, 1, res) - - res, cached, err = c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { - return 0, errors.New("ignored") - }) - assert.NilError(t, err) - assert.Assert(t, cached) - assert.Equal(t, 1, res) -} - -func TestCacheMapRecursiveCall(t *testing.T) { - t.Parallel() - c := newCacheMap[int, int]() - ctx := context.Background() - - // recursive calls that are guaranteed to result in deadlock should error out - _, _, err := c.GetOrInitialize(ctx, 1, func(ctx context.Context) (int, error) { - res, _, err := c.GetOrInitialize(ctx, 1, func(ctx context.Context) (int, error) { - return 2, nil - }) - return res, err - }) - assert.Assert(t, is.ErrorIs(err, ErrCacheMapRecursiveCall)) - - // verify same cachemap can be called recursively w/ different keys - v, _, err := c.GetOrInitialize(ctx, 10, func(ctx context.Context) (int, error) { - res, _, err := c.GetOrInitialize(ctx, 11, func(ctx context.Context) (int, error) { - return 12, nil - }) - return res, err - }) - assert.NilError(t, err) - assert.Equal(t, 12, v) - - // verify other cachemaps can be called w/ same keys - c2 := newCacheMap[int, int]() - v, cached, err := c.GetOrInitialize(ctx, 100, func(ctx context.Context) (int, error) { - res, _, err := c2.GetOrInitialize(ctx, 100, func(ctx context.Context) (int, error) { - return 101, nil - }) - return res, err - }) - assert.NilError(t, err) - assert.Equal(t, 101, v) - assert.Assert(t, !cached) -} diff --git a/dagql/idtui/frontend_pretty.go b/dagql/idtui/frontend_pretty.go index 9fde26fc2f0..d1036927c87 100644 --- a/dagql/idtui/frontend_pretty.go +++ b/dagql/idtui/frontend_pretty.go @@ -1417,7 +1417,7 @@ func (fe *frontendPretty) renderStepLogs(out TermOutput, r *renderer, row *dagui func (fe *frontendPretty) renderStepError(out TermOutput, r *renderer, span *dagui.Span, depth int, prefix string) { for _, span := range span.Errors().Order { // only print the first line - for line := range strings.SplitSeq(span.Status.Description, "\n") { + for _, line := range strings.Split(span.Status.Description, "\n") { if line == "" { continue } diff --git a/dagql/objects.go b/dagql/objects.go index 1ddf360694f..95398a3ba98 100644 --- a/dagql/objects.go +++ b/dagql/objects.go @@ -500,7 +500,7 @@ func (r Instance[T]) call( if doNotCache { callCacheKey = "" } - val, _, postCall, err := s.Cache.GetOrInitializeWithPostCall(ctx, callCacheKey, func(ctx context.Context) (innerVal Typed, postCall func(context.Context) error, innerErr error) { + res, err := s.Cache.GetOrInitializeWithPostCall(ctx, callCacheKey, func(ctx context.Context) (innerVal Typed, postCall func(context.Context) error, innerErr error) { if s.telemetry != nil { wrappedCtx, done := s.telemetry(ctx, r, newID) defer func() { done(innerVal, false, innerErr) }() @@ -541,11 +541,10 @@ func (r Instance[T]) call( if err != nil { return nil, nil, err } - if postCall != nil { - if err := postCall(ctx); err != nil { - return nil, nil, fmt.Errorf("post-call error: %w", err) - } + if err := res.PostCall(ctx); err != nil { + return nil, nil, fmt.Errorf("post-call error: %w", err) } + val := res.Result() // If the returned val is IDable and has a different digest than the original, then // add that different digest as a cache key for this val. @@ -565,7 +564,7 @@ func (r Instance[T]) call( if digestChanged && matchesType { newID = valID - _, _, err := s.Cache.GetOrInitializeValue(ctx, valID.Digest(), val) + _, err := s.Cache.GetOrInitializeValue(ctx, valID.Digest(), val) if err != nil { return nil, nil, err } @@ -658,10 +657,11 @@ func (v ExactView) Contains(s string) bool { return s == string(v) } -type ( - FuncHandler[T Typed, A any, R any] = func(ctx context.Context, self T, args A) (R, error) - NodeFuncHandler[T Typed, A any, R any] = func(ctx context.Context, self Instance[T], args A) (R, error) -) +// not available until go1.24 (see dagger/dagger#9759) +// type ( +// FuncHandler[T Typed, A any, R any] = func(ctx context.Context, self T, args A) (R, error) +// NodeFuncHandler[T Typed, A any, R any] = func(ctx context.Context, self Instance[T], args A) (R, error) +// ) // Func is a helper for defining a field resolver and schema. // @@ -681,7 +681,9 @@ type ( // // To configure a description for the field in the schema, call .Doc on the // result. -func Func[T Typed, A any, R any](name string, fn FuncHandler[T, A, R]) Field[T] { +// +// func Func[T Typed, A any, R any](name string, fn FuncHandler[T, A, R]) Field[T] { +func Func[T Typed, A any, R any](name string, fn func(ctx context.Context, self T, args A) (R, error)) Field[T] { return NodeFunc(name, func(ctx context.Context, self Instance[T], args A) (R, error) { return fn(ctx, self.Self, args) }) @@ -690,7 +692,8 @@ func Func[T Typed, A any, R any](name string, fn FuncHandler[T, A, R]) Field[T] // FuncWithCacheKey is like Func but allows specifying a custom digest that will be used to cache the operation in dagql. func FuncWithCacheKey[T Typed, A any, R any]( name string, - fn FuncHandler[T, A, R], + // fn FuncHandler[T, A, R], + fn func(ctx context.Context, self T, args A) (R, error), cacheFn GetCacheConfigFunc[T, A], ) Field[T] { return NodeFuncWithCacheKey(name, func(ctx context.Context, self Instance[T], args A) (R, error) { @@ -700,14 +703,17 @@ func FuncWithCacheKey[T Typed, A any, R any]( // NodeFunc is the same as Func, except it passes the Instance instead of the // receiver so that you can access its ID. -func NodeFunc[T Typed, A any, R any](name string, fn NodeFuncHandler[T, A, R]) Field[T] { +// +// func NodeFunc[T Typed, A any, R any](name string, fn NodeFuncHandler[T, A, R]) Field[T] { +func NodeFunc[T Typed, A any, R any](name string, fn func(ctx context.Context, self Instance[T], args A) (R, error)) Field[T] { return NodeFuncWithCacheKey(name, fn, nil) } // NodeFuncWithCacheKey is like NodeFunc but allows specifying a custom digest that will be used to cache the operation in dagql. func NodeFuncWithCacheKey[T Typed, A any, R any]( name string, - fn NodeFuncHandler[T, A, R], + // fn NodeFuncHandler[T, A, R], + fn func(ctx context.Context, self Instance[T], args A) (R, error), cacheFn GetCacheConfigFunc[T, A], ) Field[T] { var zeroArgs A diff --git a/dagql/server.go b/dagql/server.go index cac79b62b1b..df58a7fb7c3 100644 --- a/dagql/server.go +++ b/dagql/server.go @@ -23,6 +23,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/dagger/dagger/dagql/call" + "github.com/dagger/dagger/engine/cache" ) func init() { @@ -71,19 +72,9 @@ type AroundFunc func( ) (context.Context, func(res Typed, cached bool, err error)) // Cache stores results of pure selections against Server. -type Cache interface { - GetOrInitialize( - context.Context, - digest.Digest, - func(context.Context) (Typed, error), - ) (Typed, bool, error) - GetOrInitializeWithPostCall( - context.Context, - digest.Digest, - func(context.Context) (Typed, func(context.Context) error, error), - ) (Typed, bool, func(context.Context) error, error) - GetOrInitializeValue(context.Context, digest.Digest, Typed) (Typed, bool, error) -} +type Cache = cache.Cache[digest.Digest, Typed] + +type TypedResult = cache.Result[digest.Digest, Typed] // TypeDef is a type whose sole practical purpose is to define a GraphQL type, // so it explicitly includes the Definitive interface. @@ -122,6 +113,10 @@ func NewServer[T Typed](root T) *Server { return srv } +func NewCache() Cache { + return cache.NewCache[digest.Digest, Typed]() +} + func NewDefaultHandler(es graphql.ExecutableSchema) *handler.Server { // TODO: avoid this deprecated method, and customize the options return handler.NewDefaultServer(es) //nolint: staticcheck diff --git a/docs/STYLE_GUIDE.md b/docs/STYLE_GUIDE.md index 72f7f023a74..cbcff2176d1 100644 --- a/docs/STYLE_GUIDE.md +++ b/docs/STYLE_GUIDE.md @@ -78,3 +78,36 @@ Each recipe requires only: - Snippets created specifically for the cookbook (usually stored in `./cookbook/snippets/RECIPE/LANGUAGE/FILE`) - Code listings must be presented for each language SDK unless not relevant/not technically feasible for that language (e.g. a recipe for "using a magefile" would only be relevant for Go). - Code listings must be presented in a tabbed interface with the order of tabs set to `Go`, `Python` and `TypeScript`. + +### Screen recordings + +Some screen recordings can be auto-generated with the `docs/recorder` module. + +- Generate recordings for some feature pages: + + ```shell + dagger call generate-feature-recordings --base=../current_docs/features/snippets --github-token= export --path=/tmp/out + ``` + +- Generate recordings manually for other feature pages: + + ```shell + dagger logout + export PS1="$ " >> ~/.bashrc + # run each command once to warm the cache before recording + asciinema rec --overwrite --cols=80 --rows=24 ~/images/debug-breakpoints.asc + asciinema rec --overwrite --cols=80 --rows=24 ~/images/debug-interactive.asc + asciinema rec --overwrite --cols=80 --rows=24 ~/images/service-container.asc + asciinema rec --append --cols=80 --rows=24 ~/images/service-container.asc # in separate console + asciinema rec --overwrite --cols=80 --rows=24 ~/images/service-host.asc + # manually edit all .asc files to remove closing `$ exit` + # manually edit `service-container.asc` file to insert line break `\n` between terminal outputs + cd ~/images + docker run --rm -it -u $(id -u):$(id -g) -v $PWD:/data agg .asc .gif + ``` + +- Generate recordings for some quickstart pages: + + ```shell + dagger call generate-quickstart-recordings --base=../current_docs/quickstart/snippets export --path=/tmp/out + ``` diff --git a/docs/current_docs/agents.mdx b/docs/current_docs/agents.mdx index 58676b95782..eae7c6d7aaa 100644 --- a/docs/current_docs/agents.mdx +++ b/docs/current_docs/agents.mdx @@ -50,7 +50,7 @@ Here are several example repositories containing examples of Dagger modules with ### 1. Install Dagger with llm support -*Note: the latest version is `0.17.0-llm.4`. It was released on Feb 28 2025. If you are running an older build, we recommend upgrading.* +*Note: the latest version is `0.17.0-llm.6`. It was released on March 6, 2025. If you are running an older build, we recommend upgrading.* You will need a *development version* of Dagger which adds native support for LLM prompting and tool calling. @@ -59,7 +59,7 @@ Once this feature is merged (current target is 0.17), a development build will n Install the development version of LLM-enabled Dagger: ```console -curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=0.17.0-llm.4 BIN_DIR=/usr/local/bin sh +curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=0.17.0-llm.6 BIN_DIR=/usr/local/bin sh ``` You can adjust `BIN_DIR` to customize where the `dagger` CLI is installed. @@ -68,7 +68,7 @@ Verify that your Dagger installation works: ```console $ dagger -c version -v0.17.0-llm.4 +v0.17.0-llm.6 ``` ### 2.Configure LLM endpoints @@ -136,6 +136,17 @@ ollama pull llama3.2 Note that to successfully give the LLM Dagger's API to use as a tool, the model should support tools. Here's a list curated by Ollama of models that support tools: [Ollama models supporting tools](https://ollama.com/search?c=tools) +#### Using with Azure OpenAI Service + +If you're using Azure OpenAI Service, configure your environment with the following variables: + +```plaintext +OPENAI_BASE_URL=https://.cognitiveservices.azure.com +OPENAI_API_KEY= +OPENAI_MODEL= # example: gpt-4o +OPENAI_AZURE_VERSION= # example: 2024-12-01-preview +``` + ## Run modules from the command-line Use the `dagger` CLI to load a module and call its functions. diff --git a/docs/current_docs/api/snippets/functions/arguments-array/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-array/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 6bd9ab39030..8a7b7bbfe01 100644 --- a/docs/current_docs/api/snippets/functions/arguments-array/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-array/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,11 +1,10 @@ package io.dagger.modules.mymodule; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String hello(String[] names) { String message = "Hello"; diff --git a/docs/current_docs/api/snippets/functions/arguments-boolean/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-boolean/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 0e074eb48c9..a22980b2b17 100644 --- a/docs/current_docs/api/snippets/functions/arguments-boolean/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-boolean/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,11 +1,10 @@ package io.dagger.modules.mymodule; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String hello(boolean shout) { String message = "Hello, world"; diff --git a/docs/current_docs/api/snippets/functions/arguments-container/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-container/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index ff537330a2e..eea49d92f0b 100644 --- a/docs/current_docs/api/snippets/functions/arguments-container/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-container/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -2,7 +2,6 @@ import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @@ -10,7 +9,7 @@ import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String osInfo(Container ctr) throws ExecutionException, DaggerQueryException, InterruptedException { diff --git a/docs/current_docs/api/snippets/functions/arguments-default-string/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-default-string/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 4008b78cb18..964a25a7cb4 100644 --- a/docs/current_docs/api/snippets/functions/arguments-default-string/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-default-string/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,12 +1,11 @@ package io.dagger.modules.mymodule; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Default; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String hello(@Default("world") String name) { return "Hello, %s".formatted(name); diff --git a/docs/current_docs/api/snippets/functions/arguments-directory/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-directory/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index cc2333b6aed..541bc40066c 100644 --- a/docs/current_docs/api/snippets/functions/arguments-directory/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-directory/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,19 +1,20 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String tree(Directory src, String depth) throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("alpine:latest") .withMountedDirectory("/mnt", src) .withWorkdir("/mnt") diff --git a/docs/current_docs/api/snippets/functions/arguments-file/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-file/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 28ab6899f99..15453c892df 100644 --- a/docs/current_docs/api/snippets/functions/arguments-file/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-file/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,8 +1,9 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.DaggerQueryException; import io.dagger.client.File; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @@ -10,11 +11,11 @@ import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String readFile(File source) throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("alpine:latest") .withFile("/src/myfile", source) .withExec(List.of("cat", "/src/myfile")) diff --git a/docs/current_docs/api/snippets/functions/arguments-optional/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-optional/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 8a0e0013a10..99bb9a33d9a 100644 --- a/docs/current_docs/api/snippets/functions/arguments-optional/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-optional/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,12 +1,11 @@ package io.dagger.modules.mymodule; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.Optional; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String hello(Optional name) { return "Hello, " + name.orElse("World"); diff --git a/docs/current_docs/api/snippets/functions/arguments-return-values-float/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-return-values-float/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 79d1ab3ff29..80c07a29241 100644 --- a/docs/current_docs/api/snippets/functions/arguments-return-values-float/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-return-values-float/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,11 +1,10 @@ package io.dagger.modules.mymodule; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public float addFloat(float a, float b) { return a + b; diff --git a/docs/current_docs/api/snippets/functions/arguments-return-values-integer/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-return-values-integer/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index eeb1dc10b18..6d07a7e0c8b 100644 --- a/docs/current_docs/api/snippets/functions/arguments-return-values-integer/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-return-values-integer/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,11 +1,10 @@ package io.dagger.modules.mymodule; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public Integer addInteger(int a, int b) { return a + b; diff --git a/docs/current_docs/api/snippets/functions/arguments-string/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/arguments-string/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 1dd496f0487..8111f7e5c99 100644 --- a/docs/current_docs/api/snippets/functions/arguments-string/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/arguments-string/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,18 +1,19 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.DaggerQueryException; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String getUser(String gender) throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("alpine:latest") .withExec(List.of("apk", "add", "curl")) .withExec(List.of("apk", "add", "jq")) diff --git a/docs/current_docs/api/snippets/functions/functions-complex/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/functions-complex/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 1a9d82d6f41..d86e02b8787 100644 --- a/docs/current_docs/api/snippets/functions/functions-complex/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/functions-complex/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,9 +1,10 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.Directory; import io.dagger.client.DaggerQueryException; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -11,10 +12,10 @@ /** MyModule main object */ @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String getUser() throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("alpine:latest") .withExec(List.of("apk", "add", "curl")) .withExec(List.of("apk", "add", "jq")) diff --git a/docs/current_docs/api/snippets/functions/return-values-chaining/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/return-values-chaining/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 97a6ada2602..95a024d379d 100644 --- a/docs/current_docs/api/snippets/functions/return-values-chaining/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/return-values-chaining/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,11 +1,10 @@ package io.dagger.modules.mymodule; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object -public class MyModule extends AbstractModule { +public class MyModule { public String greeting; public String name; diff --git a/docs/current_docs/api/snippets/functions/return-values-container/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/return-values-container/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 1f69f32961b..7c9d4763656 100644 --- a/docs/current_docs/api/snippets/functions/return-values-container/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/return-values-container/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,8 +1,9 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @@ -10,10 +11,10 @@ import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public Container alpineBuilder(List packages) { - Container ctr = dag.container().from("alpine:latest"); + Container ctr = dag().container().from("alpine:latest"); for (String pkg : packages) { ctr = ctr.withExec(List.of("apk", "add", pkg)); } diff --git a/docs/current_docs/api/snippets/functions/return-values-directory/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/return-values-directory/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index bd9590ab781..7957cf414ac 100644 --- a/docs/current_docs/api/snippets/functions/return-values-directory/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/return-values-directory/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,16 +1,17 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Directory; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public Directory goBuilder(Directory src, String arch, String os) { - return dag.container() + return dag().container() .from("golang:1.21") .withMountedDirectory("/src", src) .withWorkdir("/src") diff --git a/docs/current_docs/api/snippets/functions/return-values-file/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/functions/return-values-file/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 377e5fae446..3e4740ca261 100644 --- a/docs/current_docs/api/snippets/functions/return-values-file/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/functions/return-values-file/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,17 +1,18 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Directory; import io.dagger.client.File; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public File archiver(Directory src) { - return dag.container() + return dag().container() .from("alpine:latest") .withExec(List.of("apk", "add", "zip")) .withMountedDirectory("/src", src) diff --git a/docs/current_docs/api/snippets/services/bind-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/bind-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index cf7a7e99e4d..6b775426d0a 100644 --- a/docs/current_docs/api/snippets/services/bind-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/bind-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,19 +1,20 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public Service httpService() { - return dag.container() + return dag().container() .from("python") .withWorkdir("/srv") .withNewFile("index.html", "Hello, world!") @@ -25,7 +26,7 @@ public Service httpService() { @Function public String get() throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("alpine") .withServiceBinding("www", httpService()) .withExec(List.of("wget", "-O-", "http://www:8080")) diff --git a/docs/current_docs/api/snippets/services/create-interdependent-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/create-interdependent-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 83a1fcb0603..3369e9f3c56 100644 --- a/docs/current_docs/api/snippets/services/create-interdependent-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/create-interdependent-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,19 +1,20 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public Service services() throws ExecutionException, DaggerQueryException, InterruptedException { - Service svcA = dag.container() + Service svcA = dag().container() .from("nginx") .withExposedPort(80) .asService(new Container.AsServiceArguments() @@ -21,7 +22,7 @@ public Service services() throws ExecutionException, DaggerQueryException, Inter .withHostname("svca"); svcA.start(); - Service svcB = dag.container() + Service svcB = dag().container() .from("nginx") .withExposedPort(80) .asService(new Container.AsServiceArguments() diff --git a/docs/current_docs/api/snippets/services/expose-host-services-to-dagger/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/expose-host-services-to-dagger/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 711ecdbbf4b..37dad3c1bd7 100644 --- a/docs/current_docs/api/snippets/services/expose-host-services-to-dagger/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/expose-host-services-to-dagger/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,19 +1,20 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String userList(Service svc) throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("mariadb:10.11.2") .withServiceBinding("db", svc) .withExec( diff --git a/docs/current_docs/api/snippets/services/persist-service-state/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/persist-service-state/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index af7ab09ab3e..c7044cb9b1e 100644 --- a/docs/current_docs/api/snippets/services/persist-service-state/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/persist-service-state/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,16 +1,17 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { private Container.WithExecArguments execOpts = new Container.WithExecArguments().withUseEntrypoint(true); @@ -18,15 +19,15 @@ public class MyModule extends AbstractModule { @Function public Container redis() { Service redisSrv = - dag.container() + dag().container() .from("redis") .withExposedPort(6379) - .withMountedCache("/data", dag.cacheVolume("my-redis")) + .withMountedCache("/data", dag().cacheVolume("my-redis")) .withWorkdir("/data") .asService(new Container.AsServiceArguments().withUseEntrypoint(true)); Container redisCli = - dag.container() + dag().container() .from("redis") .withServiceBinding("redis-srv", redisSrv) .withEntrypoint(List.of("redis-cli", "-h", "redis-srv")); diff --git a/docs/current_docs/api/snippets/services/service-lifecycle-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/service-lifecycle-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 58f060d56d6..d66fce8f83f 100644 --- a/docs/current_docs/api/snippets/services/service-lifecycle-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/service-lifecycle-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,28 +1,29 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { /** creates Redis service and client */ @Function public String redisService() throws ExecutionException, DaggerQueryException, InterruptedException { Service redisSrv = - dag.container() + dag().container() .from("redis") .withExposedPort(6379) .asService(new Container.AsServiceArguments().withUseEntrypoint(true)); // create Redis client container - Container redisCli = dag.container().from("redis").withServiceBinding("redis-srv", redisSrv); + Container redisCli = dag().container().from("redis").withServiceBinding("redis-srv", redisSrv); // send ping from client to server return redisCli.withExec(List.of("redis-cli", "-h", "redis-srv", "ping")).stdout(); diff --git a/docs/current_docs/api/snippets/services/service-lifecycle-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/service-lifecycle-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 504d5bc04c5..871308298f9 100644 --- a/docs/current_docs/api/snippets/services/service-lifecycle-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/service-lifecycle-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,9 +1,10 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @@ -12,19 +13,19 @@ import java.util.stream.Stream; @Object -public class MyModule extends AbstractModule { +public class MyModule { /** creates Redis service and client */ @Function public String redisService() throws ExecutionException, DaggerQueryException, InterruptedException { Service redisSrv = - dag.container() + dag().container() .from("redis") .withExposedPort(6379) .asService(new Container.AsServiceArguments().withUseEntrypoint(true)); // create Redis client container - Container redisCli = dag.container().from("redis").withServiceBinding("redis-srv", redisSrv); + Container redisCli = dag().container().from("redis").withServiceBinding("redis-srv", redisSrv); List args = List.of("redis-cli", "-h", "redis-srv"); diff --git a/docs/current_docs/api/snippets/services/service-lifecycle-3/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/service-lifecycle-3/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index c98ae7162f6..3692fa98102 100644 --- a/docs/current_docs/api/snippets/services/service-lifecycle-3/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/service-lifecycle-3/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,9 +1,10 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -11,19 +12,19 @@ import java.util.stream.Stream; @Object -public class MyModule extends AbstractModule { +public class MyModule { /** creates Redis service and client */ @Function public String redisService() throws ExecutionException, DaggerQueryException, InterruptedException { Service redisSrv = - dag.container() + dag().container() .from("redis") .withExposedPort(6379) .asService(new Container.AsServiceArguments().withUseEntrypoint(true)); // create Redis client container - Container redisCli = dag.container().from("redis").withServiceBinding("redis-srv", redisSrv); + Container redisCli = dag().container().from("redis").withServiceBinding("redis-srv", redisSrv); List args = List.of("redis-cli", "-h", "redis-srv"); diff --git a/docs/current_docs/api/snippets/services/start-stop-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/start-stop-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 113bb593a24..045b4845624 100644 --- a/docs/current_docs/api/snippets/services/start-stop-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/start-stop-services/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,9 +1,10 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -11,13 +12,13 @@ import java.util.stream.Stream; @Object -public class MyModule extends AbstractModule { +public class MyModule { /** Explicitly start and stop a Redis service */ @Function public String redisService() throws ExecutionException, DaggerQueryException, InterruptedException { Service redisSrv = - dag.container() + dag().container() .from("redis") .withExposedPort(6379) .asService(new Container.AsServiceArguments().withUseEntrypoint(true)); @@ -26,7 +27,7 @@ public String redisService() // start Redis ahead of time to it stays up for the duration of the test redisSrv = redisSrv.start(); - Container redisCli = dag.container().from("redis").withServiceBinding("redis-srv", redisSrv); + Container redisCli = dag().container().from("redis").withServiceBinding("redis-srv", redisSrv); List args = List.of("redis-cli", "-h", "redis-srv"); diff --git a/docs/current_docs/api/snippets/services/test-against-db-service/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/services/test-against-db-service/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 537da04fc50..cbc481d5ab5 100644 --- a/docs/current_docs/api/snippets/services/test-against-db-service/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/api/snippets/services/test-against-db-service/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,21 +1,22 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { /** Run unit tests against a database service */ @Function public String test() throws ExecutionException, DaggerQueryException, InterruptedException { Service mariadb = - dag.container() + dag().container() .from("mariadb:10.11.2") .withEnvVariable("MARIADB_USER", "user") .withEnvVariable("MARIADB_PASSWORD", "password") @@ -27,7 +28,7 @@ public String test() throws ExecutionException, DaggerQueryException, Interrupte // get Drupal base image // install additional dependencies Container drupal = - dag.container() + dag().container() .from("drupal:10.0.7-php8.2-fpm") .withExec( List.of( diff --git a/docs/current_docs/api/snippets/state-functions/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/api/snippets/state-functions/java/src/main/java/io/dagger/modules/mymodule/MyModule.java new file mode 100644 index 00000000000..912e2dae7a2 --- /dev/null +++ b/docs/current_docs/api/snippets/state-functions/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -0,0 +1,31 @@ +package io.dagger.modules.mymodule; + +import io.dagger.module.annotation.Default; +import io.dagger.module.annotation.Function; +import io.dagger.module.annotation.Object; + +@Object +public class MyModule { + /** The greeting to use */ + public String greeting; + + /** Who to greet */ + private String name; + + public MyModule() {} + + /** + * @param greeting The greeting to use + * @param name Who to greet + */ + public MyModule(@Default("Hello") String greeting, @Default("World") String name) { + this.greeting = greeting; + this.name = name; + } + + /** Return the greeting message */ + @Function + public String message() { + return greeting + ", " + name; + } +} diff --git a/docs/current_docs/api/state.mdx b/docs/current_docs/api/state.mdx index c6f990d6b67..5fbf845fa2b 100644 --- a/docs/current_docs/api/state.mdx +++ b/docs/current_docs/api/state.mdx @@ -1,6 +1,9 @@ --- slug: /api/state --- + + + import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -42,6 +45,16 @@ Here's an example where one field is exposed as a Dagger Function, while the oth ```typescript file=./snippets/state-functions/typescript/index.ts ``` + + +Dagger will automatically expose all public fields of a class as Dagger Functions. It's also possible to expose a package, `protected` or `private` field by annotating it with the `@Function` annotation. + +In case of a field that shouldn't be serialized at all, this can be achieved by marking it as `transient` in Java. + +Here's an example where one field is exposed as a Dagger Function, while the other is not: + +```java file=./snippets/state-functions/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +``` diff --git a/docs/current_docs/ci/quickstart/snippets/build/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java b/docs/current_docs/ci/quickstart/snippets/build/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java index 34e9a79b613..ee0edd8fc0a 100644 --- a/docs/current_docs/ci/quickstart/snippets/build/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java +++ b/docs/current_docs/ci/quickstart/snippets/build/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java @@ -1,10 +1,11 @@ package io.dagger.modules.hellodagger; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; import io.dagger.client.CacheVolume; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -12,7 +13,7 @@ /** HelloDagger main object */ @Object -public class HelloDagger extends AbstractModule { +public class HelloDagger { /** Build the application container */ @Function public Container build(Directory source) @@ -25,7 +26,7 @@ public Container build(Directory source) .withExec(List.of("npm", "run", "build")) // get the build output directory .directory("./dist"); - return dag.container() + return dag().container() // start from a slim NGINX container .from("nginx:1.25-alpine") // copy the build output directory to the container diff --git a/docs/current_docs/ci/quickstart/snippets/chain/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java b/docs/current_docs/ci/quickstart/snippets/chain/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java index 8972c4ba2f5..22011f5a12b 100644 --- a/docs/current_docs/ci/quickstart/snippets/chain/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java +++ b/docs/current_docs/ci/quickstart/snippets/chain/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java @@ -1,10 +1,11 @@ package io.dagger.modules.hellodagger; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; import io.dagger.client.CacheVolume; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -12,13 +13,13 @@ /** HelloDagger main object */ @Object -public class HelloDagger extends AbstractModule { +public class HelloDagger { /** * Returns a base container */ @Function public Container base() { - return dag.container().from("cgr.dev/chainguard/wolfi-base"); + return dag().container().from("cgr.dev/chainguard/wolfi-base"); } /** diff --git a/docs/current_docs/ci/quickstart/snippets/daggerize/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java b/docs/current_docs/ci/quickstart/snippets/daggerize/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java index 0e0f5540b70..c0aac56ed52 100644 --- a/docs/current_docs/ci/quickstart/snippets/daggerize/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java +++ b/docs/current_docs/ci/quickstart/snippets/daggerize/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java @@ -1,10 +1,11 @@ package io.dagger.modules.hellodagger; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; import io.dagger.client.CacheVolume; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -12,7 +13,7 @@ /** HelloDagger main object */ @Object -public class HelloDagger extends AbstractModule { +public class HelloDagger { /** Publish the application container after building and testing it on-the-fly */ @Function public String publish(Directory source) @@ -30,7 +31,7 @@ public Container build(Directory source) .buildEnv(source) .withExec(List.of("npm", "run", "build")) .directory("./dist"); - return dag.container() + return dag().container() .from("nginx:1.25-alpine") .withDirectory("/usr/share/nginx/html", build) .withExposedPort(80); @@ -50,8 +51,8 @@ public String test(Directory source) @Function public Container buildEnv(Directory source) throws InterruptedException, ExecutionException, DaggerQueryException { - CacheVolume nodeCache = dag.cacheVolume("node"); - return dag.container() + CacheVolume nodeCache = dag().cacheVolume("node"); + return dag().container() .from("node:21-slim") .withDirectory("/src", source) .withMountedCache("/root/.npm", nodeCache) diff --git a/docs/current_docs/ci/quickstart/snippets/env/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java b/docs/current_docs/ci/quickstart/snippets/env/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java index 9e85590a37a..335a8909bc1 100644 --- a/docs/current_docs/ci/quickstart/snippets/env/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java +++ b/docs/current_docs/ci/quickstart/snippets/env/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java @@ -1,10 +1,11 @@ package io.dagger.modules.hellodagger; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; import io.dagger.client.CacheVolume; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -12,13 +13,13 @@ /** HelloDagger main object */ @Object -public class HelloDagger extends AbstractModule { +public class HelloDagger { /** Build a ready-to-use development environment */ @Function public Container buildEnv(Directory source) throws InterruptedException, ExecutionException, DaggerQueryException { - CacheVolume nodeCache = dag.cacheVolume("node"); - return dag.container() + CacheVolume nodeCache = dag().cacheVolume("node"); + return dag().container() // start from a base Node.js container .from("node:21-slim") // add the source code at /src diff --git a/docs/current_docs/ci/quickstart/snippets/publish/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java b/docs/current_docs/ci/quickstart/snippets/publish/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java index 07250249acc..b72dd926337 100644 --- a/docs/current_docs/ci/quickstart/snippets/publish/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java +++ b/docs/current_docs/ci/quickstart/snippets/publish/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java @@ -4,7 +4,6 @@ import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; import io.dagger.client.CacheVolume; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -12,7 +11,7 @@ /** HelloDagger main object */ @Object -public class HelloDagger extends AbstractModule { +public class HelloDagger { /** Publish the application container after building and testing it on-the-fly */ @Function public String publish(Directory source) diff --git a/docs/current_docs/ci/quickstart/snippets/simplify/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java b/docs/current_docs/ci/quickstart/snippets/simplify/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java index 66594715938..5476abf8940 100644 --- a/docs/current_docs/ci/quickstart/snippets/simplify/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java +++ b/docs/current_docs/ci/quickstart/snippets/simplify/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java @@ -1,7 +1,8 @@ package io.dagger.modules.hellodagger; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.*; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.DefaultPath; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @@ -10,7 +11,7 @@ /** HelloDagger main object */ @Object -public class HelloDagger extends AbstractModule { +public class HelloDagger { /** Publish the application container after building and testing it on-the-fly */ @Function public String publish(@DefaultPath("/") Directory source) @@ -24,11 +25,11 @@ public String publish(@DefaultPath("/") Directory source) @Function public Container build(@DefaultPath("/") Directory source) { Directory build = - dag.node(new Client.NodeArguments().withCtr(buildEnv(source))) + dag().node(new Client.NodeArguments().withCtr(buildEnv(source))) .commands() .run(List.of("build")) .directory("./dist"); - return dag.container() + return dag().container() .from("nginx:1.25-alpine") .withDirectory("/usr/share/nginx/html", build) .withExposedPort(80); @@ -38,7 +39,7 @@ public Container build(@DefaultPath("/") Directory source) { @Function public String test(@DefaultPath("/") Directory source) throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.node(new Client.NodeArguments().withCtr(buildEnv(source))) + return dag().node(new Client.NodeArguments().withCtr(buildEnv(source))) .commands() .run(List.of("test:unit", "run")) .stdout(); @@ -47,8 +48,8 @@ public String test(@DefaultPath("/") Directory source) /** Build a ready-to-use development environment */ @Function public Container buildEnv(@DefaultPath("/") Directory source) { - CacheVolume nodeCache = dag.cacheVolume("node"); - return dag.node(new Client.NodeArguments().withVersion("21")) + CacheVolume nodeCache = dag().cacheVolume("node"); + return dag().node(new Client.NodeArguments().withVersion("21")) .withNpm() .withSource(source) .install() diff --git a/docs/current_docs/ci/quickstart/snippets/test/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java b/docs/current_docs/ci/quickstart/snippets/test/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java index 457d96b3510..38cd91a0de7 100644 --- a/docs/current_docs/ci/quickstart/snippets/test/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java +++ b/docs/current_docs/ci/quickstart/snippets/test/java/src/main/java/io/dagger/modules/hellodagger/HelloDagger.java @@ -4,7 +4,6 @@ import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; import io.dagger.client.CacheVolume; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -12,7 +11,7 @@ /** HelloDagger main object */ @Object -public class HelloDagger extends AbstractModule { +public class HelloDagger { /** Return the result of running unit tests */ @Function public String test(Directory source) diff --git a/docs/current_docs/cookbook/snippets/secret-variable/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/cookbook/snippets/secret-variable/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 5e743fdd3e9..ee01ace648c 100644 --- a/docs/current_docs/cookbook/snippets/secret-variable/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/cookbook/snippets/secret-variable/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,19 +1,20 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.DaggerQueryException; import io.dagger.client.Secret; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String githubApi(Secret token) throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("alpine:3.17") .withSecretVariable("GITHUB_API_TOKEN", token) .withExec(List.of("apk", "add", "curl")) diff --git a/docs/current_docs/features/snippets/debugging-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/features/snippets/debugging-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 742eda4216b..0fa01d5646b 100644 --- a/docs/current_docs/features/snippets/debugging-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/features/snippets/debugging-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,17 +1,18 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.DaggerQueryException; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String foo() throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("alpine:latest") .withExec(List.of("sh", "-c", "echo hello world > /foo")) .withExec(List.of("cat", "/FOO")) // deliberate error diff --git a/docs/current_docs/features/snippets/debugging-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/features/snippets/debugging-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 69e6374236b..051c19d08ed 100644 --- a/docs/current_docs/features/snippets/debugging-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/features/snippets/debugging-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,16 +1,17 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public Container foo() { - return dag.container() + return dag().container() .from("alpine:latest") .terminal() .withExec(List.of("sh", "-c", "echo hello world > /foo")) diff --git a/docs/current_docs/features/snippets/programmable-pipelines-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/features/snippets/programmable-pipelines-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 504924870a9..650328fc111 100644 --- a/docs/current_docs/features/snippets/programmable-pipelines-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/features/snippets/programmable-pipelines-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,17 +1,18 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.Directory; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public Container build(Directory src, String arch, String os) { - return dag.container() + return dag().container() .from("golang:1.21") .withMountedDirectory("/src", src) .withWorkdir("/src") diff --git a/docs/current_docs/features/snippets/programmable-pipelines-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/features/snippets/programmable-pipelines-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index c0a300b1480..ab7232014db 100644 --- a/docs/current_docs/features/snippets/programmable-pipelines-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/features/snippets/programmable-pipelines-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,17 +1,18 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Directory; import io.dagger.client.File; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public File build(Directory src, String arch, String os) { - return dag.container() + return dag().container() .from("golang:1.21") .withMountedDirectory("/src", src) .withWorkdir("/src") diff --git a/docs/current_docs/features/snippets/secrets/go/.gitattributes b/docs/current_docs/features/snippets/secrets/go/.gitattributes new file mode 100644 index 00000000000..3a454933cbe --- /dev/null +++ b/docs/current_docs/features/snippets/secrets/go/.gitattributes @@ -0,0 +1,4 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated +/internal/telemetry/** linguist-generated diff --git a/docs/current_docs/features/snippets/secrets/go/dagger.json b/docs/current_docs/features/snippets/secrets/go/dagger.json index cde4e3ca480..1077e535567 100644 --- a/docs/current_docs/features/snippets/secrets/go/dagger.json +++ b/docs/current_docs/features/snippets/secrets/go/dagger.json @@ -1,6 +1,5 @@ { "name": "my-module", - "sdk": "go", - "source": ".", - "engineVersion": "v0.12.0" + "engineVersion": "v0.15.3", + "sdk": "go" } diff --git a/docs/current_docs/features/snippets/secrets/go/go.mod b/docs/current_docs/features/snippets/secrets/go/go.mod index 894bd53e34c..190c2d31c04 100644 --- a/docs/current_docs/features/snippets/secrets/go/go.mod +++ b/docs/current_docs/features/snippets/secrets/go/go.mod @@ -1,40 +1,50 @@ -module main +module dagger/my-module -go 1.21.7 +go 1.23.2 require ( - github.com/99designs/gqlgen v0.17.49 + github.com/99designs/gqlgen v0.17.57 github.com/Khan/genqlient v0.7.0 - github.com/vektah/gqlparser/v2 v2.5.16 + github.com/vektah/gqlparser/v2 v2.5.20 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 + go.opentelemetry.io/otel/log v0.8.0 + go.opentelemetry.io/otel/metric v1.32.0 + go.opentelemetry.io/otel/sdk v1.32.0 + go.opentelemetry.io/otel/sdk/log v0.8.0 + go.opentelemetry.io/otel/sdk/metric v1.32.0 + go.opentelemetry.io/otel/trace v1.32.0 + go.opentelemetry.io/proto/otlp v1.3.1 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/sync v0.10.0 + google.golang.org/grpc v1.68.0 ) require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/sosodev/duration v1.3.1 // indirect - go.opentelemetry.io/otel v1.27.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 - go.opentelemetry.io/otel/log v0.3.0 - go.opentelemetry.io/otel/metric v1.27.0 // indirect - go.opentelemetry.io/otel/sdk v1.27.0 - go.opentelemetry.io/otel/sdk/log v0.3.0 - go.opentelemetry.io/otel/trace v1.27.0 - go.opentelemetry.io/proto/otlp v1.3.1 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect - google.golang.org/grpc v1.64.0 - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f // indirect + google.golang.org/protobuf v1.35.2 // indirect ) + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0 + +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0 diff --git a/docs/current_docs/features/snippets/secrets/go/go.sum b/docs/current_docs/features/snippets/secrets/go/go.sum index 69ca14d1eae..df52920545b 100644 --- a/docs/current_docs/features/snippets/secrets/go/go.sum +++ b/docs/current_docs/features/snippets/secrets/go/go.sum @@ -1,62 +1,64 @@ -github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ= -github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0= +github.com/99designs/gqlgen v0.17.57 h1:Ak4p60BRq6QibxY0lEc0JnQhDurfhxA67sp02lMjmPc= +github.com/99designs/gqlgen v0.17.57/go.mod h1:Jx61hzOSTcR4VJy/HFIgXiQ5rJ0Ypw8DxWLjbYDAUw0= github.com/Khan/genqlient v0.7.0 h1:GZ1meyRnzcDTK48EjqB8t3bcfYvHArCUUvgOwpz1D4w= github.com/Khan/genqlient v0.7.0/go.mod h1:HNyy3wZvuYwmW3Y7mkoQLZsa/R5n5yIRajS1kPBvSFM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8= -github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 h1:oM0GTNKGlc5qHctWeIGTVyda4iFFalOzMZ3Ehj5rwB4= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88/go.mod h1:JGG8ebaMO5nXOPnvKEl+DiA4MGwFjCbjsxT1WHIEBPY= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 h1:ccBrA8nCY5mM0y5uO7FT0ze4S0TuFcWdDB2FxGMTjkI= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0/go.mod h1:/9pb6634zi2Lk8LYg9Q0X8Ar6jka4dkFOylBLbVQPCE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= -go.opentelemetry.io/otel/log v0.3.0 h1:kJRFkpUFYtny37NQzL386WbznUByZx186DpEMKhEGZs= -go.opentelemetry.io/otel/log v0.3.0/go.mod h1:ziCwqZr9soYDwGNbIL+6kAvQC+ANvjgG367HVcyR/ys= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/sdk/log v0.3.0 h1:GEjJ8iftz2l+XO1GF2856r7yYVh74URiF9JMcAacr5U= -go.opentelemetry.io/otel/sdk/log v0.3.0/go.mod h1:BwCxtmux6ACLuys1wlbc0+vGBd+xytjmjajwqqIul2g= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.20 h1:kPaWbhBntxoZPaNdBaIPT1Kh0i1b/onb5kXgEdP5JCo= +github.com/vektah/gqlparser/v2 v2.5.20/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -65,26 +67,19 @@ golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f h1:C1QccEa9kUwvMgEUORqQD9S17QesQijxjZ84sO82mfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/docs/current_docs/features/snippets/secrets/go/main.go b/docs/current_docs/features/snippets/secrets/go/main.go index 0dd13966e9f..593e9d3d795 100644 --- a/docs/current_docs/features/snippets/secrets/go/main.go +++ b/docs/current_docs/features/snippets/secrets/go/main.go @@ -18,3 +18,4 @@ func (m *MyModule) GithubApi( WithExec([]string{"sh", "-c", `curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`}). Stdout(ctx) } + diff --git a/docs/current_docs/features/snippets/secrets/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/features/snippets/secrets/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 5e743fdd3e9..ee01ace648c 100644 --- a/docs/current_docs/features/snippets/secrets/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/features/snippets/secrets/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,19 +1,20 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.DaggerQueryException; import io.dagger.client.Secret; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String githubApi(Secret token) throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("alpine:3.17") .withSecretVariable("GITHUB_API_TOKEN", token) .withExec(List.of("apk", "add", "curl")) diff --git a/docs/current_docs/features/snippets/services-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/features/snippets/services-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 4483a8864aa..008b3147534 100644 --- a/docs/current_docs/features/snippets/services-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/features/snippets/services-1/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,17 +1,18 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public Service httpService() { - return dag.container() + return dag().container() .from("python") .withWorkdir("/srv") .withNewFile("index.html", "Hello world!") diff --git a/docs/current_docs/features/snippets/services-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java b/docs/current_docs/features/snippets/services-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java index 711ecdbbf4b..37dad3c1bd7 100644 --- a/docs/current_docs/features/snippets/services-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java +++ b/docs/current_docs/features/snippets/services-2/java/src/main/java/io/dagger/modules/mymodule/MyModule.java @@ -1,19 +1,20 @@ package io.dagger.modules.mymodule; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.DaggerQueryException; import io.dagger.client.Service; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; import java.util.concurrent.ExecutionException; @Object -public class MyModule extends AbstractModule { +public class MyModule { @Function public String userList(Service svc) throws ExecutionException, DaggerQueryException, InterruptedException { - return dag.container() + return dag().container() .from("mariadb:10.11.2") .withServiceBinding("db", svc) .withExec( diff --git a/docs/current_docs/index.mdx b/docs/current_docs/index.mdx index e8e520eac06..a7a326e8b82 100644 --- a/docs/current_docs/index.mdx +++ b/docs/current_docs/index.mdx @@ -12,23 +12,19 @@ Dagger is an open-source runtime for composable workflows. It's perfect for syst ## Key Features -- **Reproducible execution engine**, powered by containerized functions and a declarative DAG scheduler. +- **Containerized Workflow Execution:** Transform code into containerized, composable operations. Build reproducible workflows in any language with custom environments, parallel processing, and seamless chaining. -- **Universal type system**, for strongly typed composition and discovery, across platforms and languages. +- **Universal Type System:** Mix and match components from any language with type-safe connections. Use the best tools from each ecosystem without translation headaches. -- **Powerful data layer**: out-of-the-box caching, immutable state, and data tracability. +- **Automatic Artifact Caching:** Operations produce cacheable, immutable artifacts — even for LLMs and API calls. Your workflows run faster and cost less. -- **Native SDKs for 5 languages**. Go, Typescript, Python, PHP, Java - and more on the way. +- **Built-in Observability:** Full visibility into operations with tracing, logs, and metrics. Debug complex workflows and know exactly what's happening. -- **Open ecosystem**: [Thousands of modules](https://daggerverse.dev) at your fingertips, all interoperable across languages and platforms. +- **Open Platform:** Works with any compute platform and tech stack — today and tomorrow. Ship faster, experiment freely, and don’t get locked into someone else's choices. -- **Interactive command-line environment**, for rapid prototyping and debugging. +- **LLM Augmentation:** Native integration of any LLM that automatically discovers and uses available functions in your workflow. Ship mind-blowing agents in just a few dozen lines of code. -- **Batteries-included observability**. Deep tracing, metrics (including token count), and logs, all accessible from the CLI or a web UI. - -- **Adapts to you**. Seamlessly integrate with all major compute and storage platforms, CI systems, languages, and agent frameworks. - -- **LLM augmentation**. Connect to any LLM endpoint (OpenAI, Google, Anthropic, LLama, DeepSeek, etc.) and give it access to your Dagger objects. Dagger automatically handles the agentic loop. No complicated framework needed. +- **Interactive Terminal:** Directly interact with your workflow or agents in real-time through your terminal. Prototype, test, debug, and ship even faster. diff --git a/docs/docs-graphql/schema.graphqls b/docs/docs-graphql/schema.graphqls index 64f757b0ecc..31bf034ab1a 100644 --- a/docs/docs-graphql/schema.graphqls +++ b/docs/docs-graphql/schema.graphqls @@ -2233,6 +2233,26 @@ type Module { withObject(object: TypeDefID!): Module! } +"""The client generated for the module.""" +type ModuleConfigClient { + """If true, generate the client in developer mode.""" + dev: Boolean + + """The directory the client is generated in.""" + directory: String! + + """The generator to use""" + generator: String! + + """A unique identifier for this ModuleConfigClient.""" + id: ModuleConfigClientID! +} + +""" +The `ModuleConfigClientID` scalar type represents an identifier for an object of type ModuleConfigClient. +""" +scalar ModuleConfigClientID + """ The `ModuleID` scalar type represents an identifier for an object of type Module. """ @@ -2261,6 +2281,9 @@ type ModuleSource { """ commit: String! + """The clients generated for the module.""" + configClients: [ModuleConfigClient!]! + """Whether an existing dagger.json for the module was found.""" configExists: Boolean! @@ -2289,18 +2312,6 @@ type ModuleSource { """The engine version of the module.""" engineVersion: String! - """Generates a client for the module.""" - generateClient( - """The generator to use""" - generator: String! - - """Use local SDK dependency""" - localSdk: Boolean - - """The output directory for the generated client.""" - outputDir: String! - ): Directory! - """ The generated files and directories made on top of the module source's context directory. """ @@ -2372,6 +2383,18 @@ type ModuleSource { """ version: String! + """Update the module source with a new client to generate.""" + withClient( + """Generate in developer mode""" + dev: Boolean + + """The generator to use""" + generator: String! + + """The output directory for the generated client.""" + outputDir: String! + ): ModuleSource! + """ Append the provided dependencies to the module source's dependency list. """ @@ -2721,6 +2744,9 @@ type Query { """Load a ListTypeDef from its ID.""" loadListTypeDefFromID(id: ListTypeDefID!): ListTypeDef! + """Load a ModuleConfigClient from its ID.""" + loadModuleConfigClientFromID(id: ModuleConfigClientID!): ModuleConfigClient! + """Load a Module from its ID.""" loadModuleFromID(id: ModuleID!): Module! diff --git a/docs/recorder/dagger.json b/docs/recorder/dagger.json index 3376f8802a4..7d25bf8cc72 100644 --- a/docs/recorder/dagger.json +++ b/docs/recorder/dagger.json @@ -10,12 +10,12 @@ { "name": "docker", "source": "github.com/shykes/x/docker", - "pin": "114dc8a4f4c6e4723935ff3ae636ab6906e03dd1" + "pin": "36ad35ca2d917b7fe965fa3aeb2b5f74f6dda106" }, { "name": "termcast", "source": "github.com/shykes/x/termcast", - "pin": "23cbf5eb3c8873a146a78868d600fc4becc793c0" + "pin": "36ad35ca2d917b7fe965fa3aeb2b5f74f6dda106" }, { "name": "wolfi", diff --git a/docs/recorder/go.mod b/docs/recorder/go.mod index a9560f13fb5..a6917ff2951 100644 --- a/docs/recorder/go.mod +++ b/docs/recorder/go.mod @@ -6,19 +6,19 @@ require ( github.com/99designs/gqlgen v0.17.57 github.com/Khan/genqlient v0.7.0 github.com/vektah/gqlparser/v2 v2.5.20 - go.opentelemetry.io/otel v1.27.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 - go.opentelemetry.io/otel/log v0.3.0 - go.opentelemetry.io/otel/metric v1.27.0 - go.opentelemetry.io/otel/sdk v1.27.0 - go.opentelemetry.io/otel/sdk/log v0.3.0 - go.opentelemetry.io/otel/sdk/metric v1.27.0 - go.opentelemetry.io/otel/trace v1.27.0 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 + go.opentelemetry.io/otel/log v0.8.0 + go.opentelemetry.io/otel/metric v1.32.0 + go.opentelemetry.io/otel/sdk v1.32.0 + go.opentelemetry.io/otel/sdk/log v0.8.0 + go.opentelemetry.io/otel/sdk/metric v1.32.0 + go.opentelemetry.io/otel/trace v1.32.0 go.opentelemetry.io/proto/otlp v1.3.1 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/sync v0.10.0 @@ -30,21 +30,21 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/sosodev/duration v1.3.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f // indirect google.golang.org/protobuf v1.35.2 // indirect ) -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 -replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.3.0 +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0 -replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.3.0 +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0 diff --git a/docs/recorder/go.sum b/docs/recorder/go.sum index 54ca7e35efa..df52920545b 100644 --- a/docs/recorder/go.sum +++ b/docs/recorder/go.sum @@ -19,8 +19,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -31,34 +31,34 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vektah/gqlparser/v2 v2.5.20 h1:kPaWbhBntxoZPaNdBaIPT1Kh0i1b/onb5kXgEdP5JCo= github.com/vektah/gqlparser/v2 v2.5.20/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 h1:oM0GTNKGlc5qHctWeIGTVyda4iFFalOzMZ3Ehj5rwB4= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88/go.mod h1:JGG8ebaMO5nXOPnvKEl+DiA4MGwFjCbjsxT1WHIEBPY= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 h1:ccBrA8nCY5mM0y5uO7FT0ze4S0TuFcWdDB2FxGMTjkI= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0/go.mod h1:/9pb6634zi2Lk8LYg9Q0X8Ar6jka4dkFOylBLbVQPCE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 h1:bFgvUr3/O4PHj3VQcFEuYKvRZJX1SJDQ+11JXuSB3/w= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0/go.mod h1:xJntEd2KL6Qdg5lwp97HMLQDVeAhrYxmzFseAMDPQ8I= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= -go.opentelemetry.io/otel/log v0.3.0 h1:kJRFkpUFYtny37NQzL386WbznUByZx186DpEMKhEGZs= -go.opentelemetry.io/otel/log v0.3.0/go.mod h1:ziCwqZr9soYDwGNbIL+6kAvQC+ANvjgG367HVcyR/ys= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/sdk/log v0.3.0 h1:GEjJ8iftz2l+XO1GF2856r7yYVh74URiF9JMcAacr5U= -go.opentelemetry.io/otel/sdk/log v0.3.0/go.mod h1:BwCxtmux6ACLuys1wlbc0+vGBd+xytjmjajwqqIul2g= -go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= -go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -73,10 +73,10 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f h1:C1QccEa9kUwvMgEUORqQD9S17QesQijxjZ84sO82mfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/docs/recorder/main.go b/docs/recorder/main.go index 07742fb694d..6851c907917 100644 --- a/docs/recorder/main.go +++ b/docs/recorder/main.go @@ -4,6 +4,7 @@ import ( "context" "dagger/recorder/internal/dagger" "errors" + "time" ) type Recorder struct { @@ -15,24 +16,55 @@ type Recorder struct { func New( // Working directory for the recording container // +optional - werkdir *dagger.Directory, + wdir *dagger.Directory, ) Recorder { - if werkdir == nil { - werkdir = dag.Directory() + if wdir == nil { + wdir = dag.Directory() } return Recorder{ - R: dag.Termcast(dagger.TermcastOpts{ - Container: dag.Wolfi(). - Container(dagger.WolfiContainerOpts{Packages: []string{"docker-cli"}}). - WithFile("/bin/dagger", dag.DaggerCli().Binary()). - WithWorkdir("/src"). - WithMountedDirectory(".", werkdir). - WithEnvVariable("DOCKER_HOST", "tcp://docker:2375"). - WithServiceBinding("docker", dag.Docker().Engine(dagger.DockerEngineOpts{Persist: false})), - }), + R: getTermcast(wdir), } } +func getTermcast(wdir *dagger.Directory) *dagger.Termcast { + return dag.Termcast(dagger.TermcastOpts{ + Container: dag.Wolfi(). + Container(dagger.WolfiContainerOpts{Packages: []string{"docker-cli"}}). + WithFile("/bin/dagger", dag.DaggerCli().Binary()). + WithWorkdir("/src"). + WithMountedDirectory(".", wdir). + WithEnvVariable("DOCKER_HOST", "tcp://docker:2375"). + WithServiceBinding("docker", dag.Docker().Engine(dagger.DockerEngineOpts{Persist: false})), + }) +} + +func getTermcastWithEnvVar(wdir *dagger.Directory, name string, value string) *dagger.Termcast { + ctr := getTermcast(wdir).Container(). + WithEnvVariable("CACHEBUSTER", time.Now().String()). + WithEnvVariable(name, value) + return dag.Termcast().WithContainer(ctr) +} + +func getTermcastWithFile(wdir *dagger.Directory, path string, contents string) *dagger.Termcast { + ctr := getTermcast(wdir).Container().WithNewFile(path, contents) + return dag.Termcast().WithContainer(ctr) +} + +func getTermcastWithQuickstart(wdir *dagger.Directory) *dagger.Termcast { + repo := dag.Git("https://github.com/dagger/hello-dagger"). + Branch("main").Tree() + + ctr := getTermcast(dag.Directory()).Container(). + WithMountedDirectory("/src", repo). + WithMountedDirectory("/module", wdir). + WithExec([]string{"apk", "add", "curl"}). + WithExec([]string{"cp", "-R", "/module", "/src/dagger"}). + WithExec([]string{"mv", "/src/dagger/dagger.json", "/src/dagger.json"}). + WithExec([]string{"sh", "-c", `sed -i 's/"source": "."/"source": "dagger"/' /src/dagger.json`}). + WithWorkdir("/src") + return dag.Termcast().WithContainer(ctr) +} + func (r Recorder) ExecWithCacheControl(ctx context.Context, cmd string, useCache bool) (Recorder, error) { if useCache { // Dry-run to warm cache @@ -76,8 +108,8 @@ func (r Recorder) Debug(ctx context.Context) (Recorder, error) { return r, err } -func (r Recorder) Cd(werkdir string) Recorder { - r.R = r.R.WithContainer(r.R.Container().WithWorkdir(werkdir)) +func (r Recorder) Cd(wdir string) Recorder { + r.R = r.R.WithContainer(r.R.Container().WithWorkdir(wdir)) return r } @@ -87,3 +119,95 @@ func (r Recorder) Gif(ctx context.Context) (*dagger.File, error) { } return r.R.Gif(), nil } + +func (r Recorder) GenerateFeatureRecordings( + ctx context.Context, + // path to features/snippets dir + base *dagger.Directory, + githubToken *dagger.Secret, +) *dagger.Directory { + pt, _ := githubToken.Plaintext(ctx) + return dag.Directory(). + // for https://docs.dagger.io/features/programmable-pipelines + WithFile( + "build.gif", + getTermcast(base.Directory("programmable-pipelines-1/go")). + Exec("dagger call build --src=https://github.com/golang/example#master:/hello --arch=amd64 --os=linux", dagger.TermcastExecOpts{Fast: true}). + Gif()). + // for https://docs.dagger.io/features/programmable-pipelines + WithFile( + "build-publish.gif", + getTermcast(base.Directory("programmable-pipelines-1/go")). + Exec("dagger call build --src=https://github.com/golang/example#master:/hello --arch=amd64 --os=linux publish --address=ttl.sh/my-img", dagger.TermcastExecOpts{Fast: true}). + Gif()). + // for https://docs.dagger.io/features/programmable-pipelines + WithFile( + "build-export.gif", + getTermcast(base.Directory("programmable-pipelines-2/go")). + Exec("dagger call build --src=https://github.com/golang/example#master:/hello --arch=amd64 --os=linux export --path=/tmp/out", dagger.TermcastExecOpts{Fast: true}). + Gif()). + // https://docs.dagger.io/features/secrets + WithFile( + "secrets-env.gif", + getTermcastWithEnvVar(base.Directory("secrets/go"), "TOKEN", pt). + Exec("dagger call github-api --token=env://TOKEN", dagger.TermcastExecOpts{Fast: true}). + Gif()). + // for https://docs.dagger.io/features/secrets + WithFile( + "secrets-file.gif", + getTermcastWithFile(base.Directory("secrets/go"), "/token.asc", pt). + Exec("dagger call github-api --token=file:///token.asc", dagger.TermcastExecOpts{Fast: true}). + Gif()). + // for https://docs.dagger.io/features/visualization + WithFile( + "tui.gif", + getTermcast(base.Directory(".")). + Exec("dagger -m github.com/jpadams/daggerverse/trivy@v0.5.0 call scan-container --ctr=index.docker.io/alpine:latest", dagger.TermcastExecOpts{Fast: true}). + Gif()) +} + +func (r Recorder) GenerateQuickstartRecordings( + // path to quickstart/snippets dir + base *dagger.Directory, +) *dagger.Directory { + return dag.Directory(). + // for https://docs.dagger.io/quickstart/env + WithFile( + "buildenv.gif", + getTermcastWithQuickstart(base.Directory("daggerize/go")). + Exec("dagger call build-env --source=.", dagger.TermcastExecOpts{Fast: true}). + Gif()). + // for https://docs.dagger.io/quickstart/test + WithFile( + "test.gif", + getTermcastWithQuickstart(base.Directory("daggerize/go")). + Exec("dagger call test --source=.", dagger.TermcastExecOpts{Fast: true}). + Gif()). + // for https://docs.dagger.io/quickstart/build + WithFile( + "build.gif", + getTermcastWithQuickstart(base.Directory("daggerize/go")). + Exec("dagger call build --source=.", dagger.TermcastExecOpts{Fast: true}). + Gif()). + /* + // for https://docs.dagger.io/quickstart/build + WithFile( + "build-service.gif", + getTermcastWithQuickstart(base.Directory("daggerize/go")). + Exec("dagger call build --source=. as-service up --ports=8080:80", dagger.TermcastExecOpts{Fast: true}). + Gif()). + */ + // for https://docs.dagger.io/quickstart/publish + WithFile( + "publish.gif", + getTermcastWithQuickstart(base.Directory("daggerize/go")). + Exec("dagger call publish --source=.", dagger.TermcastExecOpts{Fast: true}). + Gif()). + // for https://docs.dagger.io/quickstart/publish + WithFile( + "docker.gif", + getTermcastWithQuickstart(base.Directory("daggerize/go")). + Exec("docker run --rm --detach --publish 8080:80 ttl.sh/hello-dagger-4362349", dagger.TermcastExecOpts{Fast: true}). + Exec("curl localhost:8080", dagger.TermcastExecOpts{Fast: true}). + Gif()) +} diff --git a/docs/static/api/reference/index.html b/docs/static/api/reference/index.html index af08bea69fd..9c98eeb3e44 100644 --- a/docs/static/api/reference/index.html +++ b/docs/static/api/reference/index.html @@ -69,6 +69,7 @@
  • loadInterfaceTypeDefFromID
  • loadLabelFromID
  • loadListTypeDefFromID
  • +
  • loadModuleConfigClientFromID
  • loadModuleFromID
  • loadModuleSourceFromID
  • loadObjectTypeDefFromID
  • @@ -154,6 +155,8 @@
  • ListTypeDef
  • ListTypeDefID
  • Module
  • +
  • ModuleConfigClient
  • +
  • ModuleConfigClientID
  • ModuleID
  • ModuleSource
  • ModuleSourceID
  • @@ -2352,6 +2355,59 @@
    Example
    +
    +
    + Queries +
    +

    + loadModuleConfigClientFromID +

    +
    +
    +
    +
    Description
    +

    Load a ModuleConfigClient from its ID.

    +
    +
    +
    +
    +
    +
    +
    Type
    +

    + ModuleConfigClient! +

    +
    +
    +
    Arguments
    + + + + + + + + + + + + + +
    NameDescription
    + id - ModuleConfigClientID! + +
    +
    +
    +
    +
    +
    +
    +
    Example
    +
    +
    +
    +
    Queries @@ -7641,6 +7697,63 @@
    object<
    +
    +
    + Types +
    +

    ModuleConfigClient

    +
    +
    +
    +
    Description
    +

    The client generated for the module.

    +
    +
    +
    Fields
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Field NameDescription
    dev - Boolean If true, generate the client in developer mode.
    directory - String! The directory the client is generated in.
    generator - String! The generator to use
    id - ModuleConfigClientID! A unique identifier for this ModuleConfigClient.
    +
    +
    +
    +
    +
    +
    + Types +
    +

    ModuleConfigClientID

    +
    +
    +
    +
    Description
    +

    The ModuleConfigClientID scalar type represents an identifier for an object of type ModuleConfigClient.

    +
    +
    +
    +
    Types @@ -7692,6 +7805,10 @@
    Fields
    commit - String! The resolved commit of the git repo this source points to. Only valid for git sources. + + configClients - [ModuleConfigClient!]! + The clients generated for the module. + configExists - Boolean! Whether an existing dagger.json for the module was found. @@ -7729,31 +7846,6 @@
    pathengineVersion - String! The engine version of the module. - - generateClient - Directory! - Generates a client for the module. - - - -
    -
    Arguments
    -
    -
    -
    generator - String!
    -

    The generator to use

    -
    -
    -
    localSdk - Boolean
    -

    Use local SDK dependency

    -
    -
    -
    outputDir - String!
    -

    The output directory for the generated client.

    -
    -
    -
    - - generatedContextDirectory - Directory! The generated files and directories made on top of the module source's context directory. @@ -7818,6 +7910,31 @@
    outputDirversion - String! The specified version of the git repo this source points to. Only valid for git sources. + + withClient - ModuleSource! + Update the module source with a new client to generate. + + + +
    +
    Arguments
    +
    +
    +
    dev - Boolean
    +

    Generate in developer mode

    +
    +
    +
    generator - String!
    +

    The generator to use

    +
    +
    +
    outputDir - String!
    +

    The output directory for the generated client.

    +
    +
    +
    + + withDependencies - ModuleSource! Append the provided dependencies to the module source's dependency list. diff --git a/docs/static/reference/dagger.schema.json b/docs/static/reference/dagger.schema.json index 438615b51fc..f18e592c8da 100644 --- a/docs/static/reference/dagger.schema.json +++ b/docs/static/reference/dagger.schema.json @@ -13,6 +13,28 @@ "additionalProperties": false, "type": "object" }, + "ModuleConfigClient": { + "properties": { + "generator": { + "type": "string", + "description": "The generator the client uses to be generated." + }, + "directory": { + "type": "string", + "description": "The directory the client is generated in." + }, + "localLibrary": { + "type": "boolean", + "description": "Whether the client is generated in Dev mode or not. If set using an official SDK like Go or Typescript, the client will use the local SDK library instead of the published one." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "generator", + "directory" + ] + }, "ModuleConfigDependency": { "properties": { "name": { @@ -81,6 +103,13 @@ }, "type": "array", "description": "Paths to explicitly exclude from the module, relative to the configuration file. Deprecated: Use !\u003cpattern\u003e in the include list instead." + }, + "clients": { + "items": { + "$ref": "#/$defs/ModuleConfigClient" + }, + "type": "array", + "description": "The clients generated for this module." } }, "additionalProperties": false, diff --git a/engine/cache/cache.go b/engine/cache/cache.go new file mode 100644 index 00000000000..96488eb13c4 --- /dev/null +++ b/engine/cache/cache.go @@ -0,0 +1,201 @@ +package cache + +import ( + "context" + "fmt" + "sync" +) + +type Cache[K comparable, V any] interface { + // Using the given key, either return an already cached value for that key or initialize + // an entry in the cache with the given value for that key. + GetOrInitializeValue(context.Context, K, V) (Result[K, V], error) + + // Using the given key, either return an already cached value for that key or initialize a + // new value using the given function. If the function returns an error, the error is returned. + GetOrInitialize( + context.Context, + K, + func(context.Context) (V, error), + ) (Result[K, V], error) + + // Using the given key, either return an already cached value for that key or initialize a + // new value using the given function. If the function returns an error, the error is returned. + // The function can also return an optional post call function that will be set on the returned + // result so callers of this function can call it when post-processing of all results (cached or + // not) is needed. + GetOrInitializeWithPostCall( + context.Context, + K, + func(context.Context) (V, PostCallFunc, error), + ) (Result[K, V], error) +} + +type Result[K comparable, V any] interface { + Result() V + Release() + PostCall(context.Context) error +} + +type PostCallFunc = func(context.Context) error + +var ErrCacheRecursiveCall = fmt.Errorf("recursive call detected") + +func NewCache[K comparable, V any]() Cache[K, V] { + return &cache[K, V]{} +} + +type cache[K comparable, V any] struct { + mu sync.Mutex + calls map[K]*result[K, V] +} + +type result[K comparable, V any] struct { + cache *cache[K, V] + + key K + val V + postCall PostCallFunc + err error + + done chan struct{} + cancel context.CancelCauseFunc + waiters int + + refCount int +} + +type cacheContextKey[K comparable, V any] struct { + key K + cache *cache[K, V] +} + +func (c *cache[K, V]) GetOrInitializeValue( + ctx context.Context, + key K, + val V, +) (Result[K, V], error) { + return c.GetOrInitialize(ctx, key, func(_ context.Context) (V, error) { + return val, nil + }) +} + +func (c *cache[K, V]) GetOrInitialize( + ctx context.Context, + key K, + fn func(context.Context) (V, error), +) (Result[K, V], error) { + return c.GetOrInitializeWithPostCall(ctx, key, func(ctx context.Context) (V, PostCallFunc, error) { + val, err := fn(ctx) + return val, nil, err + }) +} + +func (c *cache[K, V]) GetOrInitializeWithPostCall( + ctx context.Context, + key K, + fn func(context.Context) (V, PostCallFunc, error), +) (Result[K, V], error) { + var zeroKey K + if key == zeroKey { + // don't cache, don't dedupe calls, just call it + res := &result[K, V]{} + res.val, res.postCall, res.err = fn(ctx) + return res, res.err + } + + if ctx.Value(cacheContextKey[K, V]{key, c}) != nil { + return nil, ErrCacheRecursiveCall + } + + c.mu.Lock() + if c.calls == nil { + c.calls = make(map[K]*result[K, V]) + } + + if res, ok := c.calls[key]; ok { + // already an ongoing call + res.waiters++ + c.mu.Unlock() + return c.wait(ctx, key, res) + } + + // make a new call with ctx that's only canceled when all caller contexts are canceled + callCtx := context.WithValue(ctx, cacheContextKey[K, V]{key, c}, struct{}{}) + callCtx, cancel := context.WithCancelCause(context.WithoutCancel(callCtx)) + res := &result[K, V]{ + cache: c, + + key: key, + + done: make(chan struct{}), + cancel: cancel, + waiters: 1, + } + c.calls[key] = res + go func() { + defer close(res.done) + res.val, res.postCall, res.err = fn(callCtx) + }() + + c.mu.Unlock() + return c.wait(ctx, key, res) +} + +func (c *cache[K, V]) wait(ctx context.Context, key K, res *result[K, V]) (*result[K, V], error) { + // wait for either the call to be done or the caller's ctx to be canceled + var err error + select { + case <-res.done: + err = res.err + case <-ctx.Done(): + err = context.Cause(ctx) + } + + c.mu.Lock() + defer c.mu.Unlock() + + res.waiters-- + if res.waiters == 0 { + // no one else is waiting, can cancel the callCtx + res.cancel(err) + } + + if err == nil { + res.refCount++ + return res, nil + } + + if res.refCount == 0 { + // error happened and no refs left, delete it now + delete(c.calls, key) + } + return nil, err +} + +func (res *result[K, V]) Result() V { + return res.val +} + +func (res *result[K, V]) Release() { + if res.cache == nil { + // wasn't cached, nothing to do + return + } + + res.cache.mu.Lock() + defer res.cache.mu.Unlock() + + res.refCount-- + if res.refCount == 0 && res.waiters == 0 { + // no refs left and no one waiting on it, delete from cache + delete(res.cache.calls, res.key) + } +} + +func (res *result[K, V]) PostCall(ctx context.Context) error { + if res.postCall == nil { + return nil + } + return res.postCall(ctx) +} diff --git a/engine/cache/cache_test.go b/engine/cache/cache_test.go new file mode 100644 index 00000000000..c00fbc962df --- /dev/null +++ b/engine/cache/cache_test.go @@ -0,0 +1,288 @@ +package cache + +import ( + "context" + "errors" + "fmt" + "sync" + "testing" + "time" + + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestCacheConcurrent(t *testing.T) { + t.Parallel() + c := NewCache[int, int]() + ctx := context.Background() + + commonKey := 42 + initialized := map[int]bool{} + + wg := new(sync.WaitGroup) + for i := 0; i < 100; i++ { + i := i + wg.Add(1) + go func() { + defer wg.Done() + res, err := c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { + initialized[i] = true + return i, nil + }) + assert.NilError(t, err) + assert.Assert(t, initialized[res.Result()]) + }() + } + + wg.Wait() + + // only one of them should have initialized + assert.Assert(t, is.Len(initialized, 1)) +} + +func TestCacheErrors(t *testing.T) { + t.Parallel() + c := NewCache[int, int]() + ctx := context.Background() + + commonKey := 42 + + myErr := errors.New("nope") + _, err := c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { + return 0, myErr + }) + assert.Assert(t, is.ErrorIs(err, myErr)) + + otherErr := errors.New("nope 2") + _, err = c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { + return 0, otherErr + }) + assert.Assert(t, is.ErrorIs(err, otherErr)) + + res, err := c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { + return 1, nil + }) + assert.NilError(t, err) + assert.Equal(t, 1, res.Result()) + + res, err = c.GetOrInitialize(ctx, commonKey, func(_ context.Context) (int, error) { + return 0, errors.New("ignored") + }) + assert.NilError(t, err) + assert.Equal(t, 1, res.Result()) +} + +func TestCacheRecursiveCall(t *testing.T) { + t.Parallel() + c := NewCache[int, int]() + ctx := context.Background() + + // recursive calls that are guaranteed to result in deadlock should error out + _, err := c.GetOrInitialize(ctx, 1, func(ctx context.Context) (int, error) { + _, err := c.GetOrInitialize(ctx, 1, func(ctx context.Context) (int, error) { + return 2, nil + }) + return 0, err + }) + assert.Assert(t, is.ErrorIs(err, ErrCacheRecursiveCall)) + + // verify same cachemap can be called recursively w/ different keys + v, err := c.GetOrInitialize(ctx, 10, func(ctx context.Context) (int, error) { + res, err := c.GetOrInitialize(ctx, 11, func(ctx context.Context) (int, error) { + return 12, nil + }) + return res.Result(), err + }) + assert.NilError(t, err) + assert.Equal(t, 12, v.Result()) + + // verify other cachemaps can be called w/ same keys + c2 := NewCache[int, int]() + v, err = c.GetOrInitialize(ctx, 100, func(ctx context.Context) (int, error) { + res, err := c2.GetOrInitialize(ctx, 100, func(ctx context.Context) (int, error) { + return 101, nil + }) + return res.Result(), err + }) + assert.NilError(t, err) + assert.Equal(t, 101, v.Result()) +} + +func TestCacheContextCancel(t *testing.T) { + t.Run("cancels after all are canceled", func(t *testing.T) { + t.Parallel() + c := NewCache[int, int]() + + ctx1, cancel1 := context.WithCancel(context.Background()) + ctx2, cancel2 := context.WithCancel(context.Background()) + ctx3, cancel3 := context.WithCancel(context.Background()) + + errCh1 := make(chan error, 1) + started1 := make(chan struct{}) + go func() { + defer close(errCh1) + _, err := c.GetOrInitialize(ctx1, 1, func(ctx context.Context) (int, error) { + close(started1) + <-ctx.Done() + return 0, fmt.Errorf("oh no 1") + }) + errCh1 <- err + }() + select { + case <-started1: + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for started1") + } + + errCh2 := make(chan error, 1) + go func() { + defer close(errCh2) + _, err := c.GetOrInitialize(ctx2, 1, func(ctx context.Context) (int, error) { + <-ctx.Done() + return 1, fmt.Errorf("oh no 2") + }) + errCh2 <- err + }() + + errCh3 := make(chan error, 1) + go func() { + defer close(errCh3) + _, err := c.GetOrInitialize(ctx3, 1, func(ctx context.Context) (int, error) { + return 2, fmt.Errorf("oh no 3") + }) + errCh3 <- err + }() + + cancel2() + select { + case err := <-errCh2: + is.ErrorIs(err, context.Canceled) + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for errCh2") + } + select { + case err := <-errCh1: + t.Fatal("unexpected error from 1st client", err) + case err := <-errCh3: + t.Fatal("unexpected error from 3rd client", err) + default: + } + + cancel3() + select { + case err := <-errCh3: + is.ErrorIs(err, context.Canceled) + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for errCh3") + } + select { + case err := <-errCh1: + t.Fatal("unexpected error from 1st client", err) + default: + } + + cancel1() + select { + case err := <-errCh1: + is.ErrorIs(err, context.Canceled) + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for errCh1") + } + }) + + t.Run("succeeds if others are canceled", func(t *testing.T) { + t.Parallel() + c := NewCache[int, int]() + + ctx1, cancel1 := context.WithCancel(context.Background()) + t.Cleanup(cancel1) + ctx2, cancel2 := context.WithCancel(context.Background()) + + resCh1 := make(chan Result[int, int], 1) + errCh1 := make(chan error, 1) + started1 := make(chan struct{}) + stop1 := make(chan struct{}) + go func() { + defer close(resCh1) + defer close(errCh1) + res, err := c.GetOrInitialize(ctx1, 1, func(ctx context.Context) (int, error) { + close(started1) + <-stop1 + return 0, nil + }) + resCh1 <- res + errCh1 <- err + }() + select { + case <-started1: + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for started1") + } + + errCh2 := make(chan error, 1) + go func() { + defer close(errCh2) + _, err := c.GetOrInitialize(ctx2, 1, func(ctx context.Context) (int, error) { + <-ctx.Done() + return 1, fmt.Errorf("oh no") + }) + errCh2 <- err + }() + + cancel2() + select { + case err := <-errCh2: + is.ErrorIs(err, context.Canceled) + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for errCh2") + } + + close(stop1) + select { + case res := <-resCh1: + assert.Equal(t, 0, res.Result()) + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for resCh1") + } + select { + case err := <-errCh1: + assert.NilError(t, err) + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for errCh1") + } + }) +} + +func TestCacheResultRelease(t *testing.T) { + t.Parallel() + cacheIface := NewCache[int, int]() + c, ok := cacheIface.(*cache[int, int]) + assert.Assert(t, ok) + ctx := context.Background() + + res1A, err := c.GetOrInitialize(ctx, 1, func(_ context.Context) (int, error) { + return 1, nil + }) + assert.NilError(t, err) + res1B, err := c.GetOrInitialize(ctx, 1, func(_ context.Context) (int, error) { + return 1, nil + }) + assert.NilError(t, err) + + res2, err := c.GetOrInitialize(ctx, 2, func(_ context.Context) (int, error) { + return 2, nil + }) + assert.NilError(t, err) + + assert.Equal(t, 2, len(c.calls)) + + res2.Release() + assert.Equal(t, 1, len(c.calls)) + + res1A.Release() + assert.Equal(t, 1, len(c.calls)) + + res1B.Release() + assert.Equal(t, 0, len(c.calls)) +} diff --git a/engine/cache/manager.go b/engine/cache/cachemanager/manager.go similarity index 99% rename from engine/cache/manager.go rename to engine/cache/cachemanager/manager.go index 2b515018b88..e725c100278 100644 --- a/engine/cache/manager.go +++ b/engine/cache/cachemanager/manager.go @@ -1,4 +1,4 @@ -package cache +package cachemanager import ( "context" diff --git a/engine/cache/mountsync.go b/engine/cache/cachemanager/mountsync.go similarity index 99% rename from engine/cache/mountsync.go rename to engine/cache/cachemanager/mountsync.go index 296165300c7..5cc214ac407 100644 --- a/engine/cache/mountsync.go +++ b/engine/cache/cachemanager/mountsync.go @@ -1,4 +1,4 @@ -package cache +package cachemanager import ( "bufio" diff --git a/engine/cache/provider.go b/engine/cache/cachemanager/provider.go similarity index 99% rename from engine/cache/provider.go rename to engine/cache/cachemanager/provider.go index bb86dddbc96..0bfc4c81029 100644 --- a/engine/cache/provider.go +++ b/engine/cache/cachemanager/provider.go @@ -1,4 +1,4 @@ -package cache +package cachemanager import ( "context" diff --git a/engine/cache/service.go b/engine/cache/cachemanager/service.go similarity index 99% rename from engine/cache/service.go rename to engine/cache/cachemanager/service.go index 681a67d926e..c84af0ce0c8 100644 --- a/engine/cache/service.go +++ b/engine/cache/cachemanager/service.go @@ -1,4 +1,4 @@ -package cache +package cachemanager import ( "context" diff --git a/engine/cache/util.go b/engine/cache/cachemanager/util.go similarity index 95% rename from engine/cache/util.go rename to engine/cache/cachemanager/util.go index 2f5cc9921d9..4afb52cd773 100644 --- a/engine/cache/util.go +++ b/engine/cache/cachemanager/util.go @@ -1,4 +1,4 @@ -package cache +package cachemanager import ( "fmt" diff --git a/engine/cache/util_test.go b/engine/cache/cachemanager/util_test.go similarity index 97% rename from engine/cache/util_test.go rename to engine/cache/cachemanager/util_test.go index 44e5fe84207..4d9ffc68500 100644 --- a/engine/cache/util_test.go +++ b/engine/cache/cachemanager/util_test.go @@ -1,4 +1,4 @@ -package cache +package cachemanager import ( "io" diff --git a/engine/client/secretprovider/op.go b/engine/client/secretprovider/op.go index e2f1cbd0e7b..50745195d50 100644 --- a/engine/client/secretprovider/op.go +++ b/engine/client/secretprovider/op.go @@ -14,7 +14,7 @@ func opProvider(ctx context.Context, key string) ([]byte, error) { key = "op://" + key // Attempt to use the `OP_SERVICE_ACCOUNT_TOKEN` - if _, ok := os.LookupEnv("OP_SERVICE_ACCOUNT_TOKEN"); ok { + if os.Getenv("OP_SERVICE_ACCOUNT_TOKEN") != "" { return opSDKProvider(ctx, key) } diff --git a/engine/distconsts/consts.go b/engine/distconsts/consts.go index 9ae33c783cd..ac8760f039a 100644 --- a/engine/distconsts/consts.go +++ b/engine/distconsts/consts.go @@ -25,7 +25,7 @@ const ( AlpineVersion = "3.20.2" AlpineImage = "alpine:" + AlpineVersion - GolangVersion = "1.24.1" + GolangVersion = "1.23.6" GolangImage = "golang:" + GolangVersion + "-alpine" BusyboxVersion = "1.37.0" diff --git a/engine/server/server.go b/engine/server/server.go index ced286efa94..b3458ec59de 100644 --- a/engine/server/server.go +++ b/engine/server/server.go @@ -70,7 +70,7 @@ import ( "github.com/dagger/dagger/engine" "github.com/dagger/dagger/engine/buildkit" - daggercache "github.com/dagger/dagger/engine/cache" + daggercache "github.com/dagger/dagger/engine/cache/cachemanager" "github.com/dagger/dagger/engine/clientdb" "github.com/dagger/dagger/engine/distconsts" "github.com/dagger/dagger/engine/slog" diff --git a/engine/server/session.go b/engine/server/session.go index 48a49a6d704..263639bd786 100644 --- a/engine/server/session.go +++ b/engine/server/session.go @@ -47,7 +47,7 @@ import ( "github.com/dagger/dagger/dagql/call" "github.com/dagger/dagger/engine" "github.com/dagger/dagger/engine/buildkit" - "github.com/dagger/dagger/engine/cache" + "github.com/dagger/dagger/engine/cache/cachemanager" "github.com/dagger/dagger/engine/server/resource" "github.com/dagger/dagger/engine/slog" enginetel "github.com/dagger/dagger/engine/telemetry" @@ -262,7 +262,7 @@ func (srv *Server) initializeDaggerSession( engine.Version, runtime.GOOS, runtime.GOARCH, - srv.SolverCache.ID() != cache.LocalCacheID, + srv.SolverCache.ID() != cachemanager.LocalCacheID, ), CloudToken: clientMetadata.CloudToken, }) diff --git a/engine/session/gitcredential.go b/engine/session/gitcredential.go index c8d836450a9..cf7cd0ccc14 100644 --- a/engine/session/gitcredential.go +++ b/engine/session/gitcredential.go @@ -85,8 +85,10 @@ func (s GitCredentialAttachable) GetCredential(ctx context.Context, req *GitCred cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0", - "SSH_ASKPASS=echo", ) + if req.Protocol != "http" && req.Protocol != "https" { + cmd.Env = append(cmd.Env, "SSH_ASKPASS=echo") + } // Run the command if err := cmd.Run(); err != nil { diff --git a/engine/sources/local/localfs.go b/engine/sources/local/localfs.go index e2a0a6a944b..bf960f05a29 100644 --- a/engine/sources/local/localfs.go +++ b/engine/sources/local/localfs.go @@ -12,7 +12,7 @@ import ( "time" "github.com/containerd/continuity/sysx" - "github.com/moby/buildkit/cache" + bkcache "github.com/moby/buildkit/cache" bkcontenthash "github.com/moby/buildkit/cache/contenthash" "github.com/moby/buildkit/session" "github.com/moby/buildkit/snapshot" @@ -25,6 +25,7 @@ import ( "golang.org/x/sys/unix" "dagger.io/dagger/telemetry" + "github.com/dagger/dagger/engine/cache" "github.com/dagger/dagger/engine/contenthash" ) @@ -38,9 +39,10 @@ type localFSSharedState struct { // a given client rootPath string - // g is the singleflight group we use to dedupe/cache changes made to the local fs across + // changeCache is the cache we use to dedupe/cache changes made to the local fs across // different syncs (see docs on localFS.Sync for more info) - g SingleflightGroup[string, *ChangeWithStat] + // changeCache SingleflightGroup[string, *ChangeWithStat] + changeCache cache.Cache[string, *ChangeWithStat] } type ChangeWithStat struct { @@ -48,6 +50,8 @@ type ChangeWithStat struct { stat *HashedStatInfo } +type CachedChange = cache.Result[string, *ChangeWithStat] + // localFS holds the state for a single sync of a client's fs into our cache type localFS struct { *localFSSharedState @@ -106,11 +110,11 @@ func newLocalFS(sharedState *localFSSharedState, subdir string, includes, exclud func (local *localFS) Sync( //nolint:gocyclo ctx context.Context, remote ReadFS, - cacheManager cache.Accessor, + cacheManager bkcache.Accessor, session session.Group, forParents bool, -) (_ cache.ImmutableRef, rerr error) { - var newCopyRef cache.MutableRef // the mutable ref we will copy into with the frozen files+dirs if needed +) (_ bkcache.ImmutableRef, rerr error) { + var newCopyRef bkcache.MutableRef // the mutable ref we will copy into with the frozen files+dirs if needed var cacheCtx bkcontenthash.CacheContext // track file+dir hashes // skip creating a cache ref if we're only syncing parent dirs @@ -146,7 +150,7 @@ func (local *localFS) Sync( //nolint:gocyclo // We need to release all the cache results from local.g once we are done here to indicate that we no longer // care about any future changes made to the paths we hit during the sync, allowing any future changes made // on the client filesystem to be synced in without a conflict error. - var cachedResults []*CachedResult[string, *ChangeWithStat] + var cachedResults []CachedChange var cachedResultsMu sync.Mutex defer func() { for _, cachedResult := range cachedResults { @@ -438,9 +442,9 @@ func (local *localFS) toRootPath(path string) string { // Instead, the WriteFile method stores the hash in an xattr, which we just read here. // // Unlike other methods below, we don't need to verifyExpectedChange since there was no change applied to the path. -func (local *localFS) GetPreviousChange(ctx context.Context, path string, stat *types.Stat) (*CachedResult[string, *ChangeWithStat], error) { +func (local *localFS) GetPreviousChange(ctx context.Context, path string, stat *types.Stat) (CachedChange, error) { rootPath := local.toRootPath(path) - return local.g.Do(ctx, rootPath, func(_ context.Context) (*ChangeWithStat, error) { + return local.changeCache.GetOrInitialize(ctx, rootPath, func(_ context.Context) (*ChangeWithStat, error) { fullPath := local.toFullPath(path) isRegular := stat.Mode&uint32(os.ModeType) == 0 @@ -468,8 +472,8 @@ func (local *localFS) GetPreviousChange(ctx context.Context, path string, stat * }) } -func (local *localFS) RemoveAll(ctx context.Context, path string) (*CachedResult[string, *ChangeWithStat], error) { - appliedChange, err := local.g.Do(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { +func (local *localFS) RemoveAll(ctx context.Context, path string) (CachedChange, error) { + appliedChange, err := local.changeCache.GetOrInitialize(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { fullPath := local.toFullPath(path) if err := os.RemoveAll(fullPath); err != nil { return nil, err @@ -487,8 +491,8 @@ func (local *localFS) RemoveAll(ctx context.Context, path string) (*CachedResult return appliedChange, nil } -func (local *localFS) Mkdir(ctx context.Context, expectedChangeKind ChangeKind, path string, upperStat *types.Stat) (*CachedResult[string, *ChangeWithStat], error) { - appliedChange, err := local.g.Do(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { +func (local *localFS) Mkdir(ctx context.Context, expectedChangeKind ChangeKind, path string, upperStat *types.Stat) (CachedChange, error) { + appliedChange, err := local.changeCache.GetOrInitialize(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { fullPath := local.toFullPath(path) lowerStat, err := os.Lstat(fullPath) @@ -534,8 +538,8 @@ func (local *localFS) Mkdir(ctx context.Context, expectedChangeKind ChangeKind, return appliedChange, nil } -func (local *localFS) Symlink(ctx context.Context, expectedChangeKind ChangeKind, path string, upperStat *types.Stat) (*CachedResult[string, *ChangeWithStat], error) { - appliedChange, err := local.g.Do(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { +func (local *localFS) Symlink(ctx context.Context, expectedChangeKind ChangeKind, path string, upperStat *types.Stat) (CachedChange, error) { + appliedChange, err := local.changeCache.GetOrInitialize(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { fullPath := local.toFullPath(path) lowerStat, err := os.Lstat(fullPath) @@ -574,8 +578,8 @@ func (local *localFS) Symlink(ctx context.Context, expectedChangeKind ChangeKind return appliedChange, nil } -func (local *localFS) Hardlink(ctx context.Context, expectedChangeKind ChangeKind, path string, upperStat *types.Stat) (*CachedResult[string, *ChangeWithStat], error) { - appliedChange, err := local.g.Do(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { +func (local *localFS) Hardlink(ctx context.Context, expectedChangeKind ChangeKind, path string, upperStat *types.Stat) (CachedChange, error) { + appliedChange, err := local.changeCache.GetOrInitialize(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { fullPath := local.toFullPath(path) lowerStat, err := os.Lstat(fullPath) @@ -621,8 +625,8 @@ var copyBufferPool = &sync.Pool{ }, } -func (local *localFS) WriteFile(ctx context.Context, expectedChangeKind ChangeKind, path string, upperStat *types.Stat, upperFS ReadFS) (*CachedResult[string, *ChangeWithStat], error) { - appliedChange, err := local.g.Do(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { +func (local *localFS) WriteFile(ctx context.Context, expectedChangeKind ChangeKind, path string, upperStat *types.Stat, upperFS ReadFS) (CachedChange, error) { + appliedChange, err := local.changeCache.GetOrInitialize(ctx, local.toRootPath(path), func(ctx context.Context) (*ChangeWithStat, error) { reader, err := upperFS.ReadFile(ctx, path) if err != nil { return nil, fmt.Errorf("failed to read file %q: %w", path, err) diff --git a/engine/sources/local/singleflight.go b/engine/sources/local/singleflight.go deleted file mode 100644 index 60c2b25f9a5..00000000000 --- a/engine/sources/local/singleflight.go +++ /dev/null @@ -1,102 +0,0 @@ -package local - -import ( - "context" - "sync" -) - -// SingleflightGroup is similar to sync.Singleflight but: -// 1. Handles context cancellation (ctx provided to callback is only cancelled once no one is waiting on the result) -// 2. Caches results until all returned CachedResults are released -type SingleflightGroup[K comparable, V any] struct { - mu sync.Mutex - calls map[K]*CachedResult[K, V] -} - -type CachedResult[K comparable, V any] struct { - g *SingleflightGroup[K, V] - - key K - val V - err error - - done chan struct{} - cancel context.CancelCauseFunc - waiters int - - refCount int -} - -func (g *SingleflightGroup[K, V]) Do(ctx context.Context, key K, fn func(context.Context) (V, error)) (*CachedResult[K, V], error) { - g.mu.Lock() - if g.calls == nil { - g.calls = make(map[K]*CachedResult[K, V]) - } - - if res, ok := g.calls[key]; ok { - res.waiters++ - g.mu.Unlock() - return g.wait(ctx, key, res) - } - - callCtx, cancel := context.WithCancelCause(context.WithoutCancel(ctx)) - res := &CachedResult[K, V]{ - g: g, - - key: key, - - done: make(chan struct{}), - cancel: cancel, - waiters: 1, - } - g.calls[key] = res - go func() { - defer close(res.done) - res.val, res.err = fn(callCtx) - }() - - g.mu.Unlock() - return g.wait(ctx, key, res) -} - -func (g *SingleflightGroup[K, V]) wait(ctx context.Context, key K, res *CachedResult[K, V]) (*CachedResult[K, V], error) { - var err error - select { - case <-res.done: - err = res.err - case <-ctx.Done(): - err = context.Cause(ctx) - } - - g.mu.Lock() - defer g.mu.Unlock() - - res.waiters-- - if res.waiters == 0 { - res.cancel(err) - } - - if err == nil { - res.refCount++ - return res, nil - } - - if res.refCount == 0 { - delete(g.calls, key) - } - return nil, err -} - -func (res *CachedResult[K, V]) Result() V { - return res.val -} - -func (res *CachedResult[K, V]) Release() { - res.g.mu.Lock() - defer res.g.mu.Unlock() - - res.refCount-- - if res.refCount == 0 && res.waiters == 0 { - delete(res.g.calls, res.key) - } -} diff --git a/engine/sources/local/source.go b/engine/sources/local/source.go index f7712cc3cf7..05b477f41fc 100644 --- a/engine/sources/local/source.go +++ b/engine/sources/local/source.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/moby/buildkit/cache" + bkcache "github.com/moby/buildkit/cache" bkclient "github.com/moby/buildkit/client" "github.com/moby/buildkit/session" "github.com/moby/buildkit/session/filesync" @@ -29,11 +29,12 @@ import ( "dagger.io/dagger/telemetry" "github.com/dagger/dagger/engine" + "github.com/dagger/dagger/engine/cache" "github.com/dagger/dagger/engine/client/pathutil" ) type Opt struct { - CacheAccessor cache.Accessor + CacheAccessor bkcache.Accessor } func NewSource(opt Opt) (source.Source, error) { @@ -46,7 +47,7 @@ func NewSource(opt Opt) (source.Source, error) { } type localSource struct { - cm cache.Accessor + cm bkcache.Accessor refs map[string]*filesyncCacheRef mu sync.RWMutex @@ -145,7 +146,7 @@ func (ls *localSourceHandler) CacheKey(ctx context.Context, g session.Group, ind return "session:" + ls.src.Name + ":" + digest.FromBytes(dt).String(), digest.FromBytes(dt).String(), nil, true, nil } -func (ls *localSourceHandler) Snapshot(ctx context.Context, g session.Group) (cache.ImmutableRef, error) { +func (ls *localSourceHandler) Snapshot(ctx context.Context, g session.Group) (bkcache.ImmutableRef, error) { sessionID := ls.src.SessionID if sessionID == "" { // should only happen in Dockerfile cases @@ -168,8 +169,8 @@ func (ls *localSourceHandler) Snapshot(ctx context.Context, g session.Group) (ca return ref, nil } -func (ls *localSourceHandler) snapshotWithAnySession(ctx context.Context, g session.Group) (cache.ImmutableRef, error) { - var ref cache.ImmutableRef +func (ls *localSourceHandler) snapshotWithAnySession(ctx context.Context, g session.Group) (bkcache.ImmutableRef, error) { + var ref bkcache.ImmutableRef err := ls.sm.Any(ctx, g, func(ctx context.Context, _ string, c session.Caller) error { r, err := ls.snapshot(ctx, g, c) if err != nil { @@ -190,7 +191,7 @@ func newSpan(ctx context.Context, name string) (context.Context, trace.Span) { return tr.Start(ctx, name) } -func (ls *localSourceHandler) snapshot(ctx context.Context, session session.Group, caller session.Caller) (_ cache.ImmutableRef, rerr error) { +func (ls *localSourceHandler) snapshot(ctx context.Context, session session.Group, caller session.Caller) (_ bkcache.ImmutableRef, rerr error) { ctx, span := newSpan(ctx, "filesync") defer telemetry.End(span, func() error { return rerr }) @@ -246,7 +247,7 @@ func (ls *localSourceHandler) sync( drive string, // only set for windows clients, otherwise "" session session.Group, caller session.Caller, -) (_ cache.ImmutableRef, rerr error) { +) (_ bkcache.ImmutableRef, rerr error) { // first ensure that all the parent dirs under the client's rootfs (above the given clientPath) are synced in correctly if err := ls.syncParentDirs(ctx, ref, clientPath, drive, caller); err != nil { return nil, fmt.Errorf("failed to sync parent dirs: %w", err) @@ -309,7 +310,7 @@ func (ls *localSourceHandler) syncParentDirs( } type filesyncCacheRef struct { - mutRef cache.MutableRef + mutRef bkcache.MutableRef mounter snapshot.Mounter mntPath string @@ -364,9 +365,9 @@ func (ls *localSourceHandler) getRef( if ref.mutRef == nil { ref.mutRef, err = ls.cm.New(ctx, nil, nil, - cache.CachePolicyRetain, - cache.WithRecordType(bkclient.UsageRecordTypeLocalSource), - cache.WithDescription(fmt.Sprintf("local source for %s", ls.src.SharedKeyHint)), + bkcache.CachePolicyRetain, + bkcache.WithRecordType(bkclient.UsageRecordTypeLocalSource), + bkcache.WithDescription(fmt.Sprintf("local source for %s", ls.src.SharedKeyHint)), ) if err != nil { return nil, nil, fmt.Errorf("failed to create new mutable ref: %w", err) @@ -389,7 +390,8 @@ func (ls *localSourceHandler) getRef( } ref.sharedState = &localFSSharedState{ - rootPath: ref.mntPath, + rootPath: ref.mntPath, + changeCache: cache.NewCache[string, *ChangeWithStat](), } ls.mu.Lock() @@ -426,7 +428,7 @@ const ( sharedKeyIndex = keySharedKey + ":" ) -func searchSharedKey(ctx context.Context, store cache.MetadataStore, k string) ([]CacheRefMetadata, error) { +func searchSharedKey(ctx context.Context, store bkcache.MetadataStore, k string) ([]CacheRefMetadata, error) { var results []CacheRefMetadata mds, err := store.Search(ctx, sharedKeyIndex+k, false) if err != nil { @@ -439,7 +441,7 @@ func searchSharedKey(ctx context.Context, store cache.MetadataStore, k string) ( } type CacheRefMetadata struct { - cache.RefMetadata + bkcache.RefMetadata } func (md CacheRefMetadata) setSharedKey(key string) error { diff --git a/go.mod b/go.mod index abce836bf2d..841d4f200d2 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/dagger/dagger -go 1.24 +go 1.23.0 -toolchain go1.24.1 +toolchain go1.23.6 require ( dagger.io/dagger v0.16.2 @@ -95,7 +95,7 @@ require ( github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c github.com/urfave/cli v1.22.16 github.com/vektah/gqlparser/v2 v2.5.23 - github.com/vito/bubbline v0.0.0-20250227004723-9472f5b7eb83 + github.com/vito/bubbline v0.0.0-20250304164440-1d709bc8e9a2 github.com/vito/go-sse v1.1.2 github.com/vito/midterm v0.2.2 github.com/zeebo/xxh3 v1.0.2 @@ -131,11 +131,6 @@ require ( resenje.org/singleflight v0.4.3 ) -require ( - github.com/charmbracelet/bubbles v0.20.0 // indirect - github.com/sahilm/fuzzy v0.1.1 // indirect -) - require ( dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect @@ -179,6 +174,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/bubbles v0.20.0 // indirect github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cloudflare/circl v1.6.0 // indirect @@ -282,6 +278,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect github.com/samber/lo v1.47.0 // indirect github.com/samber/slog-common v0.18.1 // indirect github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect diff --git a/go.sum b/go.sum index 85184761f77..25ab9b3bc0e 100644 --- a/go.sum +++ b/go.sum @@ -687,8 +687,8 @@ github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/vito/bubbline v0.0.0-20250227004723-9472f5b7eb83 h1:BmivYZTJJQXAVZjJaRTVZWH2a3vbFchE/e09pf2jVAo= -github.com/vito/bubbline v0.0.0-20250227004723-9472f5b7eb83/go.mod h1:+6KVEQP/dL1Q/ZPb7eegi9el3oH/dkwYDXqyKeqmiNE= +github.com/vito/bubbline v0.0.0-20250304164440-1d709bc8e9a2 h1:2lAjmi6JYUtxLvNqliMGv8tHFye8vmtzZjGqSsX0DtY= +github.com/vito/bubbline v0.0.0-20250304164440-1d709bc8e9a2/go.mod h1:/641L6H9ILAQTmBZrVkUCCNpp1H1vOoQIQzBvQ6Fm7o= github.com/vito/go-sse v1.1.2 h1:FLQ1J0tMGN7pKa3KOyZCHojYDR0Z/L/y+3ejUO3P+tM= github.com/vito/go-sse v1.1.2/go.mod h1:2wkcaQ+jtlZ94Uve8gYZjFpL68luAjssTINA2hpgcZs= github.com/vito/midterm v0.2.2 h1:Uo+nk1n/GYbh29Ha0y+NdIfMuzcUyTvRreurs4YQLYM= diff --git a/modules/alpine/main.go b/modules/alpine/main.go index 35a4180c95a..3abced39b5b 100644 --- a/modules/alpine/main.go +++ b/modules/alpine/main.go @@ -259,6 +259,24 @@ func (m *Alpine) withPkgs( Exclude: pkg.rmFileNames, }) ctr = ctr.With(pkgscript("post-install", pkg.name, pkg.postInstall)) + + if m.Distro == DistroWolfi && pkg.name == "busybox" { + // Need a bit of special casing here due to this change in wolfi's glibc package: + // https://github.com/wolfi-dev/os/commit/8229c0379cada98fb9504dd19a068dbbe2bd0d98 + // + // That change requires that the busybox symlinks are created when glibc's trigger + // executes (otherwise `/usr/bin/sh` won't exist). However, that's tricky because + // the busybox actually has a dependency on glibc in wolfi. + // + // It turns out that change didn't affect apko because apko doesn't run any scripts + // (just installs them) and has an extra hardcoded step that manually creates busybox + // symlinks. + // + // We do run scripts, but to ensure that glibc's trigger can run successfully we run + // busybox's trigger right away after it's installed, ensuring the symlinks exist + // and glibc's trigger can run successfully later. + ctr = ctr.With(pkgscript("trigger", pkg.name, pkg.trigger)) + } } for _, pkg := range alpinePkgs { ctr = ctr.With(pkgscript("trigger", pkg.name, pkg.trigger)) diff --git a/modules/go/main.go b/modules/go/main.go index 2a64c1a3495..9bc78d67a8a 100644 --- a/modules/go/main.go +++ b/modules/go/main.go @@ -23,7 +23,7 @@ func New( source *dagger.Directory, // Go version // +optional - // +default="1.24.1" + // +default="1.23.6" version string, // Use a custom module cache // +optional diff --git a/sdk/elixir/lib/dagger/gen/client.ex b/sdk/elixir/lib/dagger/gen/client.ex index 7f1a18d09ce..1e530392511 100644 --- a/sdk/elixir/lib/dagger/gen/client.ex +++ b/sdk/elixir/lib/dagger/gen/client.ex @@ -544,6 +544,19 @@ defmodule Dagger.Client do } end + @doc "Load a ModuleConfigClient from its ID." + @spec load_module_config_client_from_id(t(), Dagger.ModuleConfigClientID.t()) :: + Dagger.ModuleConfigClient.t() + def load_module_config_client_from_id(%__MODULE__{} = client, id) do + query_builder = + client.query_builder |> QB.select("loadModuleConfigClientFromID") |> QB.put_arg("id", id) + + %Dagger.ModuleConfigClient{ + query_builder: query_builder, + client: client.client + } + end + @doc "Load a Module from its ID." @spec load_module_from_id(t(), Dagger.ModuleID.t()) :: Dagger.Module.t() def load_module_from_id(%__MODULE__{} = client, id) do diff --git a/sdk/elixir/lib/dagger/gen/module_config_client.ex b/sdk/elixir/lib/dagger/gen/module_config_client.ex new file mode 100644 index 00000000000..52bcdf28053 --- /dev/null +++ b/sdk/elixir/lib/dagger/gen/module_config_client.ex @@ -0,0 +1,62 @@ +# This file generated by `dagger_codegen`. Please DO NOT EDIT. +defmodule Dagger.ModuleConfigClient do + @moduledoc "The client generated for the module." + + alias Dagger.Core.Client + alias Dagger.Core.QueryBuilder, as: QB + + @derive Dagger.ID + + defstruct [:query_builder, :client] + + @type t() :: %__MODULE__{} + + @doc "If true, generate the client in developer mode." + @spec dev(t()) :: {:ok, boolean() | nil} | {:error, term()} + def dev(%__MODULE__{} = module_config_client) do + query_builder = + module_config_client.query_builder |> QB.select("dev") + + Client.execute(module_config_client.client, query_builder) + end + + @doc "The directory the client is generated in." + @spec directory(t()) :: {:ok, String.t()} | {:error, term()} + def directory(%__MODULE__{} = module_config_client) do + query_builder = + module_config_client.query_builder |> QB.select("directory") + + Client.execute(module_config_client.client, query_builder) + end + + @doc "The generator to use" + @spec generator(t()) :: {:ok, String.t()} | {:error, term()} + def generator(%__MODULE__{} = module_config_client) do + query_builder = + module_config_client.query_builder |> QB.select("generator") + + Client.execute(module_config_client.client, query_builder) + end + + @doc "A unique identifier for this ModuleConfigClient." + @spec id(t()) :: {:ok, Dagger.ModuleConfigClientID.t()} | {:error, term()} + def id(%__MODULE__{} = module_config_client) do + query_builder = + module_config_client.query_builder |> QB.select("id") + + Client.execute(module_config_client.client, query_builder) + end +end + +defimpl Jason.Encoder, for: Dagger.ModuleConfigClient do + def encode(module_config_client, opts) do + {:ok, id} = Dagger.ModuleConfigClient.id(module_config_client) + Jason.Encode.string(id, opts) + end +end + +defimpl Nestru.Decoder, for: Dagger.ModuleConfigClient do + def decode_fields_hint(_struct, _context, id) do + {:ok, Dagger.Client.load_module_config_client_from_id(Dagger.Global.dag(), id)} + end +end diff --git a/sdk/elixir/lib/dagger/gen/module_config_client_id.ex b/sdk/elixir/lib/dagger/gen/module_config_client_id.ex new file mode 100644 index 00000000000..9912c72ccc0 --- /dev/null +++ b/sdk/elixir/lib/dagger/gen/module_config_client_id.ex @@ -0,0 +1,6 @@ +# This file generated by `dagger_codegen`. Please DO NOT EDIT. +defmodule Dagger.ModuleConfigClientID do + @moduledoc "The `ModuleConfigClientID` scalar type represents an identifier for an object of type ModuleConfigClient." + + @type t() :: String.t() +end diff --git a/sdk/elixir/lib/dagger/gen/module_source.ex b/sdk/elixir/lib/dagger/gen/module_source.ex index 76ae94de3ec..f299d7a6be3 100644 --- a/sdk/elixir/lib/dagger/gen/module_source.ex +++ b/sdk/elixir/lib/dagger/gen/module_source.ex @@ -50,6 +50,26 @@ defmodule Dagger.ModuleSource do Client.execute(module_source.client, query_builder) end + @doc "The clients generated for the module." + @spec config_clients(t()) :: {:ok, [Dagger.ModuleConfigClient.t()]} | {:error, term()} + def config_clients(%__MODULE__{} = module_source) do + query_builder = + module_source.query_builder |> QB.select("configClients") |> QB.select("id") + + with {:ok, items} <- Client.execute(module_source.client, query_builder) do + {:ok, + for %{"id" => id} <- items do + %Dagger.ModuleConfigClient{ + query_builder: + QB.query() + |> QB.select("loadModuleConfigClientFromID") + |> QB.put_arg("id", id), + client: module_source.client + } + end} + end + end + @doc "Whether an existing dagger.json for the module was found." @spec config_exists(t()) :: {:ok, boolean()} | {:error, term()} def config_exists(%__MODULE__{} = module_source) do @@ -121,23 +141,6 @@ defmodule Dagger.ModuleSource do Client.execute(module_source.client, query_builder) end - @doc "Generates a client for the module." - @spec generate_client(t(), String.t(), String.t(), [{:local_sdk, boolean() | nil}]) :: - Dagger.Directory.t() - def generate_client(%__MODULE__{} = module_source, generator, output_dir, optional_args \\ []) do - query_builder = - module_source.query_builder - |> QB.select("generateClient") - |> QB.put_arg("generator", generator) - |> QB.put_arg("outputDir", output_dir) - |> QB.maybe_put_arg("localSdk", optional_args[:local_sdk]) - - %Dagger.Directory{ - query_builder: query_builder, - client: module_source.client - } - end - @doc "The generated files and directories made on top of the module source's context directory." @spec generated_context_directory(t()) :: Dagger.Directory.t() def generated_context_directory(%__MODULE__{} = module_source) do @@ -300,6 +303,23 @@ defmodule Dagger.ModuleSource do Client.execute(module_source.client, query_builder) end + @doc "Update the module source with a new client to generate." + @spec with_client(t(), String.t(), String.t(), [{:dev, boolean() | nil}]) :: + Dagger.ModuleSource.t() + def with_client(%__MODULE__{} = module_source, generator, output_dir, optional_args \\ []) do + query_builder = + module_source.query_builder + |> QB.select("withClient") + |> QB.put_arg("generator", generator) + |> QB.put_arg("outputDir", output_dir) + |> QB.maybe_put_arg("dev", optional_args[:dev]) + + %Dagger.ModuleSource{ + query_builder: query_builder, + client: module_source.client + } + end + @doc "Append the provided dependencies to the module source's dependency list." @spec with_dependencies(t(), [Dagger.ModuleSourceID.t()]) :: Dagger.ModuleSource.t() def with_dependencies(%__MODULE__{} = module_source, dependencies) do diff --git a/sdk/go/dag/dag.gen.go b/sdk/go/dag/dag.gen.go index aa302c884f5..954acd68d78 100644 --- a/sdk/go/dag/dag.gen.go +++ b/sdk/go/dag/dag.gen.go @@ -294,6 +294,12 @@ func LoadListTypeDefFromID(id dagger.ListTypeDefID) *dagger.ListTypeDef { return client.LoadListTypeDefFromID(id) } +// Load a ModuleConfigClient from its ID. +func LoadModuleConfigClientFromID(id dagger.ModuleConfigClientID) *dagger.ModuleConfigClient { + client := initClient() + return client.LoadModuleConfigClientFromID(id) +} + // Load a Module from its ID. func LoadModuleFromID(id dagger.ModuleID) *dagger.Module { client := initClient() diff --git a/sdk/go/dagger.gen.go b/sdk/go/dagger.gen.go index f083494a345..32726672cd3 100644 --- a/sdk/go/dagger.gen.go +++ b/sdk/go/dagger.gen.go @@ -179,6 +179,9 @@ type LabelID string // The `ListTypeDefID` scalar type represents an identifier for an object of type ListTypeDef. type ListTypeDefID string +// The `ModuleConfigClientID` scalar type represents an identifier for an object of type ModuleConfigClient. +type ModuleConfigClientID string + // The `ModuleID` scalar type represents an identifier for an object of type Module. type ModuleID string @@ -5575,6 +5578,101 @@ func (r *Module) WithObject(object *TypeDef) *Module { } } +// The client generated for the module. +type ModuleConfigClient struct { + query *querybuilder.Selection + + dev *bool + directory *string + generator *string + id *ModuleConfigClientID +} + +func (r *ModuleConfigClient) WithGraphQLQuery(q *querybuilder.Selection) *ModuleConfigClient { + return &ModuleConfigClient{ + query: q, + } +} + +// If true, generate the client in developer mode. +func (r *ModuleConfigClient) Dev(ctx context.Context) (bool, error) { + if r.dev != nil { + return *r.dev, nil + } + q := r.query.Select("dev") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The directory the client is generated in. +func (r *ModuleConfigClient) Directory(ctx context.Context) (string, error) { + if r.directory != nil { + return *r.directory, nil + } + q := r.query.Select("directory") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The generator to use +func (r *ModuleConfigClient) Generator(ctx context.Context) (string, error) { + if r.generator != nil { + return *r.generator, nil + } + q := r.query.Select("generator") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this ModuleConfigClient. +func (r *ModuleConfigClient) ID(ctx context.Context) (ModuleConfigClientID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ModuleConfigClientID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ModuleConfigClient) XXX_GraphQLType() string { + return "ModuleConfigClient" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ModuleConfigClient) XXX_GraphQLIDType() string { + return "ModuleConfigClientID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ModuleConfigClient) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ModuleConfigClient) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} + // The source needed to load and run a module, along with any metadata about the source such as versions/urls/etc. type ModuleSource struct { query *querybuilder.Selection @@ -5663,6 +5761,39 @@ func (r *ModuleSource) Commit(ctx context.Context) (string, error) { return response, q.Execute(ctx) } +// The clients generated for the module. +func (r *ModuleSource) ConfigClients(ctx context.Context) ([]ModuleConfigClient, error) { + q := r.query.Select("configClients") + + q = q.Select("id") + + type configClients struct { + Id ModuleConfigClientID + } + + convert := func(fields []configClients) []ModuleConfigClient { + out := []ModuleConfigClient{} + + for i := range fields { + val := ModuleConfigClient{id: &fields[i].Id} + val.query = q.Root().Select("loadModuleConfigClientFromID").Arg("id", fields[i].Id) + out = append(out, val) + } + + return out + } + var response []configClients + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + // Whether an existing dagger.json for the module was found. func (r *ModuleSource) ConfigExists(ctx context.Context) (bool, error) { if r.configExists != nil { @@ -5754,29 +5885,6 @@ func (r *ModuleSource) EngineVersion(ctx context.Context) (string, error) { return response, q.Execute(ctx) } -// ModuleSourceGenerateClientOpts contains options for ModuleSource.GenerateClient -type ModuleSourceGenerateClientOpts struct { - // Use local SDK dependency - LocalSDK bool -} - -// Generates a client for the module. -func (r *ModuleSource) GenerateClient(generator string, outputDir string, opts ...ModuleSourceGenerateClientOpts) *Directory { - q := r.query.Select("generateClient") - for i := len(opts) - 1; i >= 0; i-- { - // `localSdk` optional argument - if !querybuilder.IsZeroValue(opts[i].LocalSDK) { - q = q.Arg("localSdk", opts[i].LocalSDK) - } - } - q = q.Arg("generator", generator) - q = q.Arg("outputDir", outputDir) - - return &Directory{ - query: q, - } -} - // The generated files and directories made on top of the module source's context directory. func (r *ModuleSource) GeneratedContextDirectory() *Directory { q := r.query.Select("generatedContextDirectory") @@ -6004,6 +6112,29 @@ func (r *ModuleSource) Version(ctx context.Context) (string, error) { return response, q.Execute(ctx) } +// ModuleSourceWithClientOpts contains options for ModuleSource.WithClient +type ModuleSourceWithClientOpts struct { + // Generate in developer mode + Dev bool +} + +// Update the module source with a new client to generate. +func (r *ModuleSource) WithClient(generator string, outputDir string, opts ...ModuleSourceWithClientOpts) *ModuleSource { + q := r.query.Select("withClient") + for i := len(opts) - 1; i >= 0; i-- { + // `dev` optional argument + if !querybuilder.IsZeroValue(opts[i].Dev) { + q = q.Arg("dev", opts[i].Dev) + } + } + q = q.Arg("generator", generator) + q = q.Arg("outputDir", outputDir) + + return &ModuleSource{ + query: q, + } +} + // Append the provided dependencies to the module source's dependency list. func (r *ModuleSource) WithDependencies(dependencies []*ModuleSource) *ModuleSource { q := r.query.Select("withDependencies") @@ -6878,6 +7009,16 @@ func (r *Client) LoadListTypeDefFromID(id ListTypeDefID) *ListTypeDef { } } +// Load a ModuleConfigClient from its ID. +func (r *Client) LoadModuleConfigClientFromID(id ModuleConfigClientID) *ModuleConfigClient { + q := r.query.Select("loadModuleConfigClientFromID") + q = q.Arg("id", id) + + return &ModuleConfigClient{ + query: q, + } +} + // Load a Module from its ID. func (r *Client) LoadModuleFromID(id ModuleID) *Module { q := r.query.Select("loadModuleFromID") diff --git a/sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/IDAbleVisitor.java b/sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/IDAbleVisitor.java index 0d99bae8566..5c2f2997d1d 100644 --- a/sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/IDAbleVisitor.java +++ b/sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/IDAbleVisitor.java @@ -7,7 +7,6 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.util.List; -import java.util.stream.Collectors; import javax.lang.model.element.Modifier; public class IDAbleVisitor extends AbstractMultiTypesVisitor { @@ -21,43 +20,18 @@ TypeSpec generateType(List types) { TypeSpec.classBuilder("JsonConverter") .addJavadoc("Convert to and from Json with the right serializers and deserializers") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addMethod( - MethodSpec.methodBuilder("serializer") - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .returns(Jsonb.class) - .addStatement("return $T.create()", JsonbBuilder.class) - .build()) - .addMethod( - MethodSpec.methodBuilder("deserializer") - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .returns(Jsonb.class) - .addParameter(ClassName.bestGuess("Client"), "dag") - .addStatement("return $T.create(getJsonbConfig(dag))", JsonbBuilder.class) - .build()) - .addMethod( - MethodSpec.methodBuilder("getJsonbConfig") - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .returns(JsonbConfig.class) - .addParameter(ClassName.bestGuess("Client"), "dag") - .addStatement( - "return new $T().withDeserializers($L)", - JsonbConfig.class, - types.stream() - .map( - t -> - CodeBlock.of( - "new $T(dag)", - ClassName.bestGuess(t.getName() + ".Deserializer")) - .toString()) - .collect(Collectors.joining(", "))) - .build()) .addMethod( MethodSpec.methodBuilder("toJSON") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(ClassName.bestGuess("JSON")) .addException(Exception.class) .addParameter(Object.class, "object") - .beginControlFlow("try ($T jsonb = serializer())", Jsonb.class) + .beginControlFlow( + "try ($T jsonb = $T.create(new $T().withPropertyVisibilityStrategy(new $T())))", + Jsonb.class, + JsonbBuilder.class, + JsonbConfig.class, + ClassName.bestGuess("io.dagger.client.FieldsStrategy")) .addStatement( "return $T.from(jsonb.toJson(object))", ClassName.bestGuess("JSON")) .endControlFlow() @@ -67,28 +41,31 @@ TypeSpec generateType(List types) { .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariable(TypeVariableName.get("T")) .returns(TypeVariableName.get("T")) - .addParameter(ClassName.bestGuess("Client"), "dag") .addParameter(ClassName.bestGuess("JSON"), "json") .addParameter( ParameterizedTypeName.get( ClassName.get(Class.class), TypeVariableName.get("T")), "clazz") .addException(Exception.class) - .addStatement("return fromJSON(dag, json.convert(), clazz)") + .addStatement("return fromJSON(json.convert(), clazz)") .build()) .addMethod( MethodSpec.methodBuilder("fromJSON") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariable(TypeVariableName.get("T")) .returns(TypeVariableName.get("T")) - .addParameter(ClassName.bestGuess("Client"), "dag") .addParameter(ClassName.get(String.class), "json") .addParameter( ParameterizedTypeName.get( ClassName.get(Class.class), TypeVariableName.get("T")), "clazz") .addException(Exception.class) - .beginControlFlow("try ($T jsonb = deserializer(dag))", Jsonb.class) + .beginControlFlow( + "try ($T jsonb = $T.create(new $T().withPropertyVisibilityStrategy(new $T())))", + Jsonb.class, + JsonbBuilder.class, + JsonbConfig.class, + ClassName.bestGuess("io.dagger.client.FieldsStrategy")) .addStatement("return jsonb.fromJson(json, clazz)") .endControlFlow() .build()); diff --git a/sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/ObjectVisitor.java b/sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/ObjectVisitor.java index 173d01e44bf..614e783f875 100644 --- a/sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/ObjectVisitor.java +++ b/sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/ObjectVisitor.java @@ -3,6 +3,7 @@ import static org.apache.commons.lang3.StringUtils.capitalize; import com.palantir.javapoet.*; +import jakarta.json.bind.annotation.JsonbTypeDeserializer; import jakarta.json.bind.annotation.JsonbTypeSerializer; import jakarta.json.bind.serializer.DeserializationContext; import jakarta.json.bind.serializer.JsonbDeserializer; @@ -45,8 +46,6 @@ TypeSpec generateType(Type type) { "connection", Modifier.PRIVATE) .build()); - // AutoCloseable implementation - classBuilder.addSuperinterface(AutoCloseable.class); MethodSpec closeMethod = MethodSpec.methodBuilder("close") .addException(Exception.class) @@ -72,6 +71,13 @@ TypeSpec generateType(Type type) { AnnotationSpec.builder(JsonbTypeSerializer.class) .addMember("value", "$T.class", ClassName.bestGuess("IDAbleSerializer")) .build()); + classBuilder.addAnnotation( + AnnotationSpec.builder(JsonbTypeDeserializer.class) + .addMember( + "value", + "$T.class", + ClassName.bestGuess(Helpers.formatName(type) + ".Deserializer")) + .build()); classBuilder.addType( TypeSpec.classBuilder("Deserializer") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) @@ -79,12 +85,6 @@ TypeSpec generateType(Type type) { ParameterizedTypeName.get( ClassName.get(JsonbDeserializer.class), ClassName.bestGuess(Helpers.formatName(type)))) - .addField(ClassName.bestGuess("Client"), "dag", Modifier.PRIVATE, Modifier.FINAL) - .addMethod( - MethodSpec.constructorBuilder() - .addParameter(ClassName.bestGuess("Client"), "dag") - .addStatement("this.dag = dag") - .build()) .addMethod( MethodSpec.methodBuilder("deserialize") .addModifiers(Modifier.PUBLIC) @@ -96,8 +96,9 @@ TypeSpec generateType(Type type) { .addStatement( "$T id = ctx.deserialize($T.class, parser)", String.class, String.class) .addStatement( - "$T o = dag.load$LFromID(new $T(id))", + "$T o = $T.dag().load$LFromID(new $T(id))", ClassName.bestGuess(Helpers.formatName(type)), + ClassName.bestGuess("io.dagger.client.Dagger"), Helpers.formatName(type), type.getIdField().getTypeRef().formatOutput()) .addStatement("return o") diff --git a/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/DaggerModuleAnnotationProcessor.java b/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/DaggerModuleAnnotationProcessor.java index d2d9f2c5c42..71342dae289 100644 --- a/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/DaggerModuleAnnotationProcessor.java +++ b/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/DaggerModuleAnnotationProcessor.java @@ -7,7 +7,6 @@ import com.google.auto.service.AutoService; import com.palantir.javapoet.*; import io.dagger.client.*; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.*; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Module; @@ -17,7 +16,6 @@ import io.dagger.module.info.ModuleInfo; import io.dagger.module.info.ObjectInfo; import io.dagger.module.info.ParameterInfo; -import jakarta.json.bind.JsonbBuilder; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.*; @@ -95,18 +93,6 @@ ModuleInfo generateModuleInfo(Set annotations, RoundEnvir "The class %s must be public if annotated with @Object".formatted(qName)); } - if (!processingEnv - .getTypeUtils() - .isSubtype( - typeElement.getSuperclass(), - processingEnv - .getElementUtils() - .getTypeElement(AbstractModule.class.getName()) - .asType())) { - throw new RuntimeException( - "The class %s must extend %s".formatted(qName, AbstractModule.class.getName())); - } - boolean hasDefaultConstructor = typeElement.getEnclosedElements().stream() .filter(elt -> elt.getKind() == ElementKind.CONSTRUCTOR) @@ -122,7 +108,7 @@ ModuleInfo generateModuleInfo(Set annotations, RoundEnvir .formatted(qName)); } - Optional constructorInfo = Optional.empty(); + Optional constructorInfo = Optional.empty(); if (mainObject) { List constructorDefs = typeElement.getEnclosedElements().stream() @@ -133,22 +119,14 @@ ModuleInfo generateModuleInfo(Set annotations, RoundEnvir Element elt = constructorDefs.get(0); constructorInfo = Optional.of( - new ConstructorInfo( - ((ExecutableElement) elt) - .getParameters() - .get(0) - .asType() - .toString() - .equals("io.dagger.client.Client"), - new FunctionInfo( - "", - "", - parseFunctionDescription(elt), - new TypeInfo( - ((ExecutableElement) elt).getReturnType().toString(), - ((ExecutableElement) elt).getReturnType().getKind().name()), - parseParameters((ExecutableElement) elt) - .toArray(new ParameterInfo[0])))); + new FunctionInfo( + "", + "", + parseFunctionDescription(elt), + new TypeInfo( + ((ExecutableElement) elt).getReturnType().toString(), + ((ExecutableElement) elt).getReturnType().getKind().name()), + parseParameters((ExecutableElement) elt).toArray(new ParameterInfo[0]))); } else if (constructorDefs.size() > 1) { // There's more than one non-empty constructor, but Dagger only supports to expose a // single one @@ -160,10 +138,13 @@ ModuleInfo generateModuleInfo(Set annotations, RoundEnvir List fieldInfoInfos = typeElement.getEnclosedElements().stream() .filter(elt -> elt.getKind() == ElementKind.FIELD) - .filter(elt -> elt.getModifiers().contains(Modifier.PUBLIC)) + .filter(elt -> !elt.getModifiers().contains(Modifier.TRANSIENT)) + .filter(elt -> !elt.getModifiers().contains(Modifier.STATIC)) + .filter(elt -> !elt.getModifiers().contains(Modifier.FINAL)) .filter( elt -> - !elt.getModifiers().containsAll(List.of(Modifier.STATIC, Modifier.FINAL))) + elt.getModifiers().contains(Modifier.PUBLIC) + || elt.getAnnotation(Function.class) != null) .map( elt -> { String fieldName = elt.getSimpleName().toString(); @@ -322,20 +303,21 @@ static JavaFile generate(ModuleInfo moduleInfo) { .addException(ExecutionException.class) .addException(DaggerQueryException.class) .addException(InterruptedException.class) - .addCode("$T module = dag.module()", io.dagger.client.Module.class); + .addCode( + "$T module = $T.dag().module()", io.dagger.client.Module.class, Dagger.class); if (isNotBlank(moduleInfo.description())) { rm.addCode("\n .withDescription($S)", moduleInfo.description()); } for (var objectInfo : moduleInfo.objects()) { rm.addCode("\n .withObject(") - .addCode("\n dag.typeDef().withObject($S", objectInfo.name()); + .addCode("\n $T.dag().typeDef().withObject($S", Dagger.class, objectInfo.name()); if (isNotBlank(objectInfo.description())) { rm.addCode( ", new $T.WithObjectArguments().withDescription($S)", TypeDef.class, objectInfo.description()); } - rm.addCode(")"); // end of dag.TypeDef().withObject( + rm.addCode(")"); // end of dag().TypeDef().withObject( for (var fnInfo : objectInfo.functions()) { rm.addCode("\n .withFunction(") .addCode(withFunction(objectInfo, fnInfo)) @@ -353,7 +335,7 @@ static JavaFile generate(ModuleInfo moduleInfo) { } if (objectInfo.constructor().isPresent()) { rm.addCode("\n .withConstructor(") - .addCode(withFunction(objectInfo, objectInfo.constructor().get().constructor())) + .addCode(withFunction(objectInfo, objectInfo.constructor().get())) .addCode(")"); // end of .withConstructor } rm.addCode(")"); // end of .withObject( @@ -370,8 +352,7 @@ static JavaFile generate(ModuleInfo moduleInfo) { .addParameter(String.class, "parentName") .addParameter(String.class, "fnName") .addParameter( - ParameterizedTypeName.get(Map.class, String.class, JSON.class), "inputArgs") - .beginControlFlow("try (var jsonb = $T.create())", JsonbBuilder.class); + ParameterizedTypeName.get(Map.class, String.class, JSON.class), "inputArgs"); var firstObj = true; for (var objectInfo : moduleInfo.objects()) { if (firstObj) { @@ -387,12 +368,10 @@ static JavaFile generate(ModuleInfo moduleInfo) { ClassName objName = ClassName.bestGuess(objectInfo.qualifiedName()); im.addStatement("$T clazz = Class.forName($S)", Class.class, objectInfo.qualifiedName()) .addStatement( - "$T obj = ($T) $T.fromJSON(dag, parentJson, clazz)", + "$T obj = ($T) $T.fromJSON(parentJson, clazz)", objName, objName, - JsonConverter.class) - .addStatement( - "clazz.getMethod(\"setClient\", $T.class).invoke(obj, dag)", Client.class); + JsonConverter.class); } var firstFn = true; for (var fnInfo : objectInfo.functions()) { @@ -419,8 +398,7 @@ static JavaFile generate(ModuleInfo moduleInfo) { im.endControlFlow(); // functions } } - im.endControlFlow(); // objects - im.endControlFlow() // try json + im.endControlFlow() // objects .addStatement( "throw new $T(new $T(\"unknown function \" + fnName))", InvocationTargetException.class, @@ -430,23 +408,18 @@ static JavaFile generate(ModuleInfo moduleInfo) { JavaFile.builder( "io.dagger.gen.entrypoint", TypeSpec.classBuilder("Entrypoint") - .addField( - FieldSpec.builder(Client.class, "dag", Modifier.PRIVATE, Modifier.FINAL) - .build()) .addModifiers(Modifier.PUBLIC) - .addMethod( - MethodSpec.constructorBuilder() - .addParameter(Client.class, "dag") - .addStatement("this.dag = dag") - .build()) + .addMethod(MethodSpec.constructorBuilder().build()) .addMethod( MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addException(Exception.class) .returns(void.class) .addParameter(String[].class, "args") - .beginControlFlow("try (Client dag = $T.connect())", Dagger.class) - .addStatement("new Entrypoint(dag).dispatch()") + .beginControlFlow("try") + .addStatement("new Entrypoint().dispatch()") + .nextControlFlow("finally") + .addStatement("$T.dag().close()", Dagger.class) .endControlFlow() .build()) .addMethod( @@ -455,7 +428,9 @@ static JavaFile generate(ModuleInfo moduleInfo) { .returns(void.class) .addException(Exception.class) .addStatement( - "$T fnCall = dag.currentFunctionCall()", FunctionCall.class) + "$T fnCall = $T.dag().currentFunctionCall()", + FunctionCall.class, + Dagger.class) .beginControlFlow("try") .addStatement("$T parentName = fnCall.parentName()", String.class) .addStatement("$T fnName = fnCall.name()", String.class) @@ -485,10 +460,13 @@ static JavaFile generate(ModuleInfo moduleInfo) { .addStatement("fnCall.returnValue(result)") .nextControlFlow("catch ($T e)", InvocationTargetException.class) .addStatement( - "fnCall.returnError(dag.error(e.getTargetException().getMessage()))") + "fnCall.returnError($T.dag().error(e.getTargetException().getMessage()))", + Dagger.class) .addStatement("throw e") .nextControlFlow("catch ($T e)", Exception.class) - .addStatement("fnCall.returnError(dag.error(e.getMessage()))") + .addStatement( + "fnCall.returnError($T.dag().error(e.getMessage()))", + Dagger.class) .addStatement("throw e") .endControlFlow() .build()) @@ -497,6 +475,7 @@ static JavaFile generate(ModuleInfo moduleInfo) { .build()) .addFileComment("This class has been generated by dagger-java-sdk. DO NOT EDIT.") .indent(" ") + .addStaticImport(Dagger.class, "dag") .build(); return f; @@ -505,17 +484,7 @@ static JavaFile generate(ModuleInfo moduleInfo) { } } - private static CodeBlock functionInvoke(ObjectInfo objectInfo, ConstructorInfo constructorInfo) { - return functionInvoke( - objectInfo, constructorInfo.constructor(), constructorInfo.hasDaggerClient()); - } - private static CodeBlock functionInvoke(ObjectInfo objectInfo, FunctionInfo fnInfo) { - return functionInvoke(objectInfo, fnInfo, false); - } - - private static CodeBlock functionInvoke( - ObjectInfo objectInfo, FunctionInfo fnInfo, boolean hasDaggerClientInConstructor) { CodeBlock.Builder code = CodeBlock.builder(); CodeBlock fnReturnType = typeName(fnInfo.returnType()); @@ -524,11 +493,10 @@ private static CodeBlock functionInvoke( // the object initialization has been skipped, it has to be done here code.addStatement("$T clazz = Class.forName($S)", Class.class, objectInfo.qualifiedName()) .addStatement( - "$T obj = ($T) $T.fromJSON(dag, parentJson, clazz)", + "$T obj = ($T) $T.fromJSON(parentJson, clazz)", objName, objName, - JsonConverter.class) - .addStatement("obj.setClient(dag)"); + JsonConverter.class); } for (var parameterInfo : fnInfo.parameters()) { @@ -555,7 +523,7 @@ private static CodeBlock functionInvoke( .add("$L = (", parameterInfo.name()) .add(paramType) .add( - ") $T.fromJSON(dag, inputArgs.get($S), ", + ") $T.fromJSON(inputArgs.get($S), ", JsonConverter.class, parameterInfo.name()) .add(paramType) @@ -581,12 +549,6 @@ private static CodeBlock functionInvoke( if (objectInfo.constructor().isPresent() && fnInfo.name().equals("")) { code.add("$T res = new $T(", objName, objName); - if (hasDaggerClientInConstructor) { - code.add("dag"); - if (fnInfo.parameters().length > 0) { - code.add(", "); - } - } } else { code.add(fnReturnType).add(" res = obj.$L(", fnInfo.qName()); } @@ -613,7 +575,10 @@ public static CodeBlock withFunction(ObjectInfo objectInfo, FunctionInfo fnInfo) boolean isConstructor = fnInfo.name().equals(""); CodeBlock.Builder code = CodeBlock.builder() - .add("\n dag.function($S,", isConstructor ? "" : fnInfo.name()) + .add( + "\n $T.dag().function($S,", + Dagger.class, + isConstructor ? "" : fnInfo.name()) .add("\n ") .add( isConstructor @@ -686,18 +651,26 @@ static CodeBlock typeDef(TypeInfo ti) throws ClassNotFoundException { String name = ti.typeName(); if (name.equals("int")) { return CodeBlock.of( - "dag.typeDef().withKind($T.$L)", TypeDefKind.class, TypeDefKind.INTEGER_KIND.name()); + "$T.dag().typeDef().withKind($T.$L)", + Dagger.class, + TypeDefKind.class, + TypeDefKind.INTEGER_KIND.name()); } else if (name.equals("boolean")) { return CodeBlock.of( - "dag.typeDef().withKind($T.$L)", TypeDefKind.class, TypeDefKind.BOOLEAN_KIND.name()); + "$T.dag().typeDef().withKind($T.$L)", + Dagger.class, + TypeDefKind.class, + TypeDefKind.BOOLEAN_KIND.name()); } else if (name.startsWith("java.util.List<")) { name = name.substring("java.util.List<".length(), name.length() - 1); - return CodeBlock.of("dag.typeDef().withListOf($L)", typeDef(tiFromName(name)).toString()); + return CodeBlock.of( + "$T.dag().typeDef().withListOf($L)", Dagger.class, typeDef(tiFromName(name)).toString()); } else if (!ti.kindName().isEmpty() && TypeKind.valueOf(ti.kindName()) == TypeKind.ARRAY) { // in that case the type name is com.example.Type[] // so we remove the [] to get the underlying type name = name.substring(0, name.length() - 2); - return CodeBlock.of("dag.typeDef().withListOf($L)", typeDef(tiFromName(name)).toString()); + return CodeBlock.of( + "$T.dag().typeDef().withListOf($L)", Dagger.class, typeDef(tiFromName(name)).toString()); } else if (name.startsWith("java.util.Optional<")) { name = name.substring("java.util.Optional<".length(), name.length() - 1); return typeName(tiFromName(name)); @@ -707,10 +680,10 @@ static CodeBlock typeDef(TypeInfo ti) throws ClassNotFoundException { var clazz = Class.forName(name); if (clazz.isEnum()) { String typeName = name.substring(name.lastIndexOf('.') + 1); - return CodeBlock.of("dag.typeDef().withEnum($S)", typeName); + return CodeBlock.of("$T.dag().typeDef().withEnum($S)", Dagger.class, typeName); } else if (Scalar.class.isAssignableFrom(clazz)) { String typeName = name.substring(name.lastIndexOf('.') + 1); - return CodeBlock.of("dag.typeDef().withScalar($S)", typeName); + return CodeBlock.of("$T.dag().typeDef().withScalar($S)", Dagger.class, typeName); } } catch (ClassNotFoundException e) { // we are ignoring here any ClassNotFoundException @@ -723,10 +696,11 @@ static CodeBlock typeDef(TypeInfo ti) throws ClassNotFoundException { } var kindName = (name + "_kind").toUpperCase(); var kind = TypeDefKind.valueOf(kindName); - return CodeBlock.of("dag.typeDef().withKind($T.$L)", TypeDefKind.class, kind.name()); + return CodeBlock.of( + "$T.dag().typeDef().withKind($T.$L)", Dagger.class, TypeDefKind.class, kind.name()); } catch (IllegalArgumentException e) { String typeName = name.substring(name.lastIndexOf('.') + 1); - return CodeBlock.of("dag.typeDef().withObject($S)", typeName); + return CodeBlock.of("$T.dag().typeDef().withObject($S)", Dagger.class, typeName); } } diff --git a/sdk/java/dagger-java-annotation-processor/src/test/java/io/dagger/annotation/processor/TypeDefTest.java b/sdk/java/dagger-java-annotation-processor/src/test/java/io/dagger/annotation/processor/TypeDefTest.java index 74f7c026e1c..9c157eae66b 100644 --- a/sdk/java/dagger-java-annotation-processor/src/test/java/io/dagger/annotation/processor/TypeDefTest.java +++ b/sdk/java/dagger-java-annotation-processor/src/test/java/io/dagger/annotation/processor/TypeDefTest.java @@ -16,7 +16,8 @@ public void testTypeDefString() throws ClassNotFoundException { DaggerModuleAnnotationProcessor.typeDef( new TypeInfo(v.getClass().getCanonicalName(), TypeKind.DECLARED.name())) .toString()) - .isEqualTo("dag.typeDef().withKind(io.dagger.client.TypeDefKind.STRING_KIND)"); + .isEqualTo( + "io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.STRING_KIND)"); } @Test @@ -26,7 +27,8 @@ public void testTypeDefInteger() throws ClassNotFoundException { DaggerModuleAnnotationProcessor.typeDef( new TypeInfo(v.getClass().getCanonicalName(), TypeKind.DECLARED.name())) .toString()) - .isEqualTo("dag.typeDef().withKind(io.dagger.client.TypeDefKind.INTEGER_KIND)"); + .isEqualTo( + "io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.INTEGER_KIND)"); } @Test @@ -34,7 +36,8 @@ public void testTypeDefInt() throws ClassNotFoundException { assertThat( DaggerModuleAnnotationProcessor.typeDef(new TypeInfo("int", TypeKind.INT.name())) .toString()) - .isEqualTo("dag.typeDef().withKind(io.dagger.client.TypeDefKind.INTEGER_KIND)"); + .isEqualTo( + "io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.INTEGER_KIND)"); } @Test @@ -43,7 +46,8 @@ public void testTypeDefBool() throws ClassNotFoundException { DaggerModuleAnnotationProcessor.typeDef( new TypeInfo("boolean", TypeKind.BOOLEAN.name())) .toString()) - .isEqualTo("dag.typeDef().withKind(io.dagger.client.TypeDefKind.BOOLEAN_KIND)"); + .isEqualTo( + "io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.BOOLEAN_KIND)"); } @Test @@ -53,7 +57,7 @@ public void testTypeDefListString() throws ClassNotFoundException { new TypeInfo("java.util.List", TypeKind.DECLARED.name())) .toString()) .isEqualTo( - "dag.typeDef().withListOf(dag.typeDef().withKind(io.dagger.client.TypeDefKind.STRING_KIND))"); + "io.dagger.client.Dagger.dag().typeDef().withListOf(io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.STRING_KIND))"); } @Test @@ -63,7 +67,8 @@ public void testTypeDefListContainer() throws ClassNotFoundException { new TypeInfo( "java.util.List", TypeKind.DECLARED.name())) .toString()) - .isEqualTo("dag.typeDef().withListOf(dag.typeDef().withObject(\"Container\"))"); + .isEqualTo( + "io.dagger.client.Dagger.dag().typeDef().withListOf(io.dagger.client.Dagger.dag().typeDef().withObject(\"Container\"))"); } @Test @@ -73,7 +78,7 @@ public void testTypeDefEnum() throws ClassNotFoundException { DaggerModuleAnnotationProcessor.typeDef( new TypeInfo(v.getClass().getCanonicalName(), TypeKind.DECLARED.name())) .toString()) - .isEqualTo("dag.typeDef().withEnum(\"ImageMediaTypes\")"); + .isEqualTo("io.dagger.client.Dagger.dag().typeDef().withEnum(\"ImageMediaTypes\")"); } @Test @@ -83,7 +88,7 @@ public void testTypeDefScalar() throws ClassNotFoundException { DaggerModuleAnnotationProcessor.typeDef( new TypeInfo(platform.getClass().getCanonicalName(), TypeKind.DECLARED.name())) .toString()) - .isEqualTo("dag.typeDef().withScalar(\"Platform\")"); + .isEqualTo("io.dagger.client.Dagger.dag().typeDef().withScalar(\"Platform\")"); } @Test @@ -94,6 +99,6 @@ public void testTypeDefArray() throws ClassNotFoundException { new TypeInfo(v.getClass().getCanonicalName(), TypeKind.ARRAY.name())) .toString()) .isEqualTo( - "dag.typeDef().withListOf(dag.typeDef().withKind(io.dagger.client.TypeDefKind.STRING_KIND))"); + "io.dagger.client.Dagger.dag().typeDef().withListOf(io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.STRING_KIND))"); } } diff --git a/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/entrypoint.java b/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/entrypoint.java index ea5b9ddecb9..acbcc800527 100644 --- a/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/entrypoint.java +++ b/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/entrypoint.java @@ -1,9 +1,9 @@ // This class has been generated by dagger-java-sdk. DO NOT EDIT. package io.dagger.gen.entrypoint; -import io.dagger.client.Client; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.Container; -import io.dagger.client.Dagger; import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; import io.dagger.client.Function; @@ -17,7 +17,6 @@ import io.dagger.client.TypeDef; import io.dagger.client.TypeDefKind; import io.dagger.java.module.DaggerJava; -import jakarta.json.bind.JsonbBuilder; import java.lang.Class; import java.lang.Error; import java.lang.Exception; @@ -32,20 +31,19 @@ import java.util.concurrent.ExecutionException; public class Entrypoint { - private final Client dag; - - Entrypoint(Client dag) { - this.dag = dag; + Entrypoint() { } public static void main(String[] args) throws Exception { - try (Client dag = Dagger.connect()) { - new Entrypoint(dag).dispatch(); + try { + new Entrypoint().dispatch(); + } finally { + dag().close(); } } private void dispatch() throws Exception { - FunctionCall fnCall = dag.currentFunctionCall(); + FunctionCall fnCall = dag().currentFunctionCall(); try { String parentName = fnCall.parentName(); String fnName = fnCall.name(); @@ -65,232 +63,219 @@ private void dispatch() throws Exception { } fnCall.returnValue(result); } catch (InvocationTargetException e) { - fnCall.returnError(dag.error(e.getTargetException().getMessage())); + fnCall.returnError(dag().error(e.getTargetException().getMessage())); throw e; } catch (Exception e) { - fnCall.returnError(dag.error(e.getMessage())); + fnCall.returnError(dag().error(e.getMessage())); throw e; } } private ModuleID register() throws ExecutionException, DaggerQueryException, InterruptedException { - Module module = dag.module() + Module module = dag().module() .withDescription("Dagger Java Module example") .withObject( - dag.typeDef().withObject("DaggerJava", new TypeDef.WithObjectArguments().withDescription("Dagger Java Module main object")) + dag().typeDef().withObject("DaggerJava", new TypeDef.WithObjectArguments().withDescription("Dagger Java Module main object")) .withFunction( - dag.function("containerEcho", - dag.typeDef().withObject("Container")) + dag().function("containerEcho", + dag().typeDef().withObject("Container")) .withDescription("Returns a container that echoes whatever string argument is provided") - .withArg("stringArg", dag.typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDescription("string to echo").withDefaultValue(JSON.from("\"Hello Dagger\"")))) + .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDescription("string to echo").withDefaultValue(JSON.from("\"Hello Dagger\"")))) .withFunction( - dag.function("grepDir", - dag.typeDef().withKind(TypeDefKind.STRING_KIND)) + dag().function("grepDir", + dag().typeDef().withKind(TypeDefKind.STRING_KIND)) .withDescription("Returns lines that match a pattern in the files of the provided Directory") - .withArg("directoryArg", dag.typeDef().withObject("Directory"), new Function.WithArgArguments().withDescription("Directory to grep").withDefaultPath("sdk/java").withIgnore(List.of("**", "!*.java"))) - .withArg("pattern", dag.typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true), new Function.WithArgArguments().withDescription("Pattern to search for in the directory"))) + .withArg("directoryArg", dag().typeDef().withObject("Directory"), new Function.WithArgArguments().withDescription("Directory to grep").withDefaultPath("sdk/java").withIgnore(List.of("**", "!*.java"))) + .withArg("pattern", dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true), new Function.WithArgArguments().withDescription("Pattern to search for in the directory"))) .withFunction( - dag.function("itself", - dag.typeDef().withObject("DaggerJava"))) + dag().function("itself", + dag().typeDef().withObject("DaggerJava"))) .withFunction( - dag.function("isZero", - dag.typeDef().withKind(TypeDefKind.BOOLEAN_KIND)) + dag().function("isZero", + dag().typeDef().withKind(TypeDefKind.BOOLEAN_KIND)) .withDescription("but this description should be exposed") - .withArg("value", dag.typeDef().withKind(TypeDefKind.INTEGER_KIND))) + .withArg("value", dag().typeDef().withKind(TypeDefKind.INTEGER_KIND))) .withFunction( - dag.function("doThings", - dag.typeDef().withListOf(dag.typeDef().withKind(io.dagger.client.TypeDefKind.INTEGER_KIND))) - .withArg("stringArray", dag.typeDef().withListOf(dag.typeDef().withKind(io.dagger.client.TypeDefKind.STRING_KIND))) - .withArg("ints", dag.typeDef().withListOf(dag.typeDef().withKind(io.dagger.client.TypeDefKind.INTEGER_KIND))) - .withArg("containers", dag.typeDef().withListOf(dag.typeDef().withObject("Container")))) + dag().function("doThings", + dag().typeDef().withListOf(io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.INTEGER_KIND))) + .withArg("stringArray", dag().typeDef().withListOf(io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.STRING_KIND))) + .withArg("ints", dag().typeDef().withListOf(io.dagger.client.Dagger.dag().typeDef().withKind(io.dagger.client.TypeDefKind.INTEGER_KIND))) + .withArg("containers", dag().typeDef().withListOf(io.dagger.client.Dagger.dag().typeDef().withObject("Container")))) .withFunction( - dag.function("nonNullableNoDefault", - dag.typeDef().withKind(TypeDefKind.STRING_KIND)) + dag().function("nonNullableNoDefault", + dag().typeDef().withKind(TypeDefKind.STRING_KIND)) .withDescription("User must provide the argument") - .withArg("stringArg", dag.typeDef().withKind(TypeDefKind.STRING_KIND))) + .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND))) .withFunction( - dag.function("nonNullableDefault", - dag.typeDef().withKind(TypeDefKind.STRING_KIND)) + dag().function("nonNullableDefault", + dag().typeDef().withKind(TypeDefKind.STRING_KIND)) .withDescription("If the user doesn't provide an argument, a default value is used. The argument can't be null.") - .withArg("stringArg", dag.typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDefaultValue(JSON.from("\"default value\"")))) + .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDefaultValue(JSON.from("\"default value\"")))) .withFunction( - dag.function("nullable", - dag.typeDef().withKind(TypeDefKind.STRING_KIND)) + dag().function("nullable", + dag().typeDef().withKind(TypeDefKind.STRING_KIND)) .withDescription("Make it optional but do not define a value. If the user doesn't provide an argument, it will be\n" + " set to null.") - .withArg("stringArg", dag.typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true))) + .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true))) .withFunction( - dag.function("nullableDefault", - dag.typeDef().withKind(TypeDefKind.STRING_KIND)) + dag().function("nullableDefault", + dag().typeDef().withKind(TypeDefKind.STRING_KIND)) .withDescription("Set a default value in case the user doesn't provide a value and allow for null value.") - .withArg("stringArg", dag.typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true), new Function.WithArgArguments().withDefaultValue(JSON.from("\"Foo\"")))) + .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true), new Function.WithArgArguments().withDefaultValue(JSON.from("\"Foo\"")))) .withFunction( - dag.function("defaultPlatform", - dag.typeDef().withScalar("Platform")) + dag().function("defaultPlatform", + dag().typeDef().withScalar("Platform")) .withDescription("return the default platform as a Scalar value")) .withFunction( - dag.function("addFloat", - dag.typeDef().withKind(TypeDefKind.FLOAT_KIND)) - .withArg("a", dag.typeDef().withKind(TypeDefKind.FLOAT_KIND)) - .withArg("b", dag.typeDef().withKind(TypeDefKind.FLOAT_KIND))) - .withField("source", dag.typeDef().withObject("Directory"), new TypeDef.WithFieldArguments().withDescription("Project source directory")) - .withField("version", dag.typeDef().withKind(TypeDefKind.STRING_KIND)) + dag().function("addFloat", + dag().typeDef().withKind(TypeDefKind.FLOAT_KIND)) + .withArg("a", dag().typeDef().withKind(TypeDefKind.FLOAT_KIND)) + .withArg("b", dag().typeDef().withKind(TypeDefKind.FLOAT_KIND))) + .withField("source", dag().typeDef().withObject("Directory"), new TypeDef.WithFieldArguments().withDescription("Project source directory")) + .withField("version", dag().typeDef().withKind(TypeDefKind.STRING_KIND)) .withConstructor( - dag.function("", - dag.typeDef().withObject("DaggerJava")) + dag().function("", + dag().typeDef().withObject("DaggerJava")) .withDescription("Initialize the DaggerJava Module") - .withArg("source", dag.typeDef().withObject("Directory").withOptional(true), new Function.WithArgArguments().withDescription("Project source directory")) - .withArg("version", dag.typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDescription("Go version").withDefaultValue(JSON.from("\"1.23.2\""))))); + .withArg("source", dag().typeDef().withObject("Directory").withOptional(true), new Function.WithArgArguments().withDescription("Project source directory")) + .withArg("version", dag().typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDescription("Go version").withDefaultValue(JSON.from("\"1.23.2\""))))); return module.id(); } private JSON invoke(JSON parentJson, String parentName, String fnName, Map inputArgs) throws Exception { - try (var jsonb = JsonbBuilder.create()) { - if (parentName.equals("DaggerJava")) { - if (fnName.equals("containerEcho")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - String stringArg = null; - if (inputArgs.get("stringArg") != null) { - stringArg = (String) JsonConverter.fromJSON(dag, inputArgs.get("stringArg"), String.class); - } - Objects.requireNonNull(stringArg, "stringArg must not be null"); - Container res = obj.containerEcho(stringArg); - return JsonConverter.toJSON(res); - } else if (fnName.equals("grepDir")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - Directory directoryArg = null; - if (inputArgs.get("directoryArg") != null) { - directoryArg = (Directory) JsonConverter.fromJSON(dag, inputArgs.get("directoryArg"), Directory.class); - } - Objects.requireNonNull(directoryArg, "directoryArg must not be null"); - String pattern = null; - if (inputArgs.get("pattern") != null) { - pattern = (String) JsonConverter.fromJSON(dag, inputArgs.get("pattern"), String.class); - } - var pattern_opt = Optional.ofNullable(pattern); - String res = obj.grepDir(directoryArg, pattern_opt); - return JsonConverter.toJSON(res); - } else if (fnName.equals("itself")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - DaggerJava res = obj.itself(); - return JsonConverter.toJSON(res); - } else if (fnName.equals("isZero")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - int value = 0; - if (inputArgs.get("value") != null) { - value = (int) JsonConverter.fromJSON(dag, inputArgs.get("value"), int.class); - } - boolean res = obj.isZero(value); - return JsonConverter.toJSON(res); - } else if (fnName.equals("doThings")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - String[] stringArray = null; - if (inputArgs.get("stringArray") != null) { - stringArray = (String[]) JsonConverter.fromJSON(dag, inputArgs.get("stringArray"), String[].class); - } - Objects.requireNonNull(stringArray, "stringArray must not be null"); - List ints = null; - if (inputArgs.get("ints") != null) { - ints = (List) JsonConverter.fromJSON(dag, inputArgs.get("ints"), List.class); - } - Objects.requireNonNull(ints, "ints must not be null"); - List containers = null; - if (inputArgs.get("containers") != null) { - containers = (List) JsonConverter.fromJSON(dag, inputArgs.get("containers"), List.class); - } - Objects.requireNonNull(containers, "containers must not be null"); - int[] res = obj.doThings(stringArray, ints, containers); - return JsonConverter.toJSON(res); - } else if (fnName.equals("nonNullableNoDefault")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - String stringArg = null; - if (inputArgs.get("stringArg") != null) { - stringArg = (String) JsonConverter.fromJSON(dag, inputArgs.get("stringArg"), String.class); - } - Objects.requireNonNull(stringArg, "stringArg must not be null"); - String res = obj.nonNullableNoDefault(stringArg); - return JsonConverter.toJSON(res); - } else if (fnName.equals("nonNullableDefault")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - String stringArg = null; - if (inputArgs.get("stringArg") != null) { - stringArg = (String) JsonConverter.fromJSON(dag, inputArgs.get("stringArg"), String.class); - } - Objects.requireNonNull(stringArg, "stringArg must not be null"); - String res = obj.nonNullableDefault(stringArg); - return JsonConverter.toJSON(res); - } else if (fnName.equals("nullable")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - String stringArg = null; - if (inputArgs.get("stringArg") != null) { - stringArg = (String) JsonConverter.fromJSON(dag, inputArgs.get("stringArg"), String.class); - } - var stringArg_opt = Optional.ofNullable(stringArg); - String res = obj.nullable(stringArg_opt); - return JsonConverter.toJSON(res); - } else if (fnName.equals("nullableDefault")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - String stringArg = null; - if (inputArgs.get("stringArg") != null) { - stringArg = (String) JsonConverter.fromJSON(dag, inputArgs.get("stringArg"), String.class); - } - var stringArg_opt = Optional.ofNullable(stringArg); - String res = obj.nullableDefault(stringArg_opt); - return JsonConverter.toJSON(res); - } else if (fnName.equals("defaultPlatform")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - Platform res = obj.defaultPlatform(); - return JsonConverter.toJSON(res); - } else if (fnName.equals("addFloat")) { - Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); - DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(dag, parentJson, clazz); - obj.setClient(dag); - float a = 0; - if (inputArgs.get("a") != null) { - a = (float) JsonConverter.fromJSON(dag, inputArgs.get("a"), float.class); - } - float b = 0; - if (inputArgs.get("b") != null) { - b = (float) JsonConverter.fromJSON(dag, inputArgs.get("b"), float.class); - } - float res = obj.addFloat(a, b); - return JsonConverter.toJSON(res); - } if (fnName.equals("")) { - Directory source = null; - if (inputArgs.get("source") != null) { - source = (Directory) JsonConverter.fromJSON(dag, inputArgs.get("source"), Directory.class); - } - var source_opt = Optional.ofNullable(source); - String version = null; - if (inputArgs.get("version") != null) { - version = (String) JsonConverter.fromJSON(dag, inputArgs.get("version"), String.class); - } - Objects.requireNonNull(version, "version must not be null"); - DaggerJava res = new DaggerJava(dag, source_opt, version); - return JsonConverter.toJSON(res); + if (parentName.equals("DaggerJava")) { + if (fnName.equals("containerEcho")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + String stringArg = null; + if (inputArgs.get("stringArg") != null) { + stringArg = (String) JsonConverter.fromJSON(inputArgs.get("stringArg"), String.class); + } + Objects.requireNonNull(stringArg, "stringArg must not be null"); + Container res = obj.containerEcho(stringArg); + return JsonConverter.toJSON(res); + } else if (fnName.equals("grepDir")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + Directory directoryArg = null; + if (inputArgs.get("directoryArg") != null) { + directoryArg = (Directory) JsonConverter.fromJSON(inputArgs.get("directoryArg"), Directory.class); + } + Objects.requireNonNull(directoryArg, "directoryArg must not be null"); + String pattern = null; + if (inputArgs.get("pattern") != null) { + pattern = (String) JsonConverter.fromJSON(inputArgs.get("pattern"), String.class); + } + var pattern_opt = Optional.ofNullable(pattern); + String res = obj.grepDir(directoryArg, pattern_opt); + return JsonConverter.toJSON(res); + } else if (fnName.equals("itself")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + DaggerJava res = obj.itself(); + return JsonConverter.toJSON(res); + } else if (fnName.equals("isZero")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + int value = 0; + if (inputArgs.get("value") != null) { + value = (int) JsonConverter.fromJSON(inputArgs.get("value"), int.class); + } + boolean res = obj.isZero(value); + return JsonConverter.toJSON(res); + } else if (fnName.equals("doThings")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + String[] stringArray = null; + if (inputArgs.get("stringArray") != null) { + stringArray = (String[]) JsonConverter.fromJSON(inputArgs.get("stringArray"), String[].class); + } + Objects.requireNonNull(stringArray, "stringArray must not be null"); + List ints = null; + if (inputArgs.get("ints") != null) { + ints = (List) JsonConverter.fromJSON(inputArgs.get("ints"), List.class); + } + Objects.requireNonNull(ints, "ints must not be null"); + List containers = null; + if (inputArgs.get("containers") != null) { + containers = (List) JsonConverter.fromJSON(inputArgs.get("containers"), List.class); + } + Objects.requireNonNull(containers, "containers must not be null"); + int[] res = obj.doThings(stringArray, ints, containers); + return JsonConverter.toJSON(res); + } else if (fnName.equals("nonNullableNoDefault")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + String stringArg = null; + if (inputArgs.get("stringArg") != null) { + stringArg = (String) JsonConverter.fromJSON(inputArgs.get("stringArg"), String.class); + } + Objects.requireNonNull(stringArg, "stringArg must not be null"); + String res = obj.nonNullableNoDefault(stringArg); + return JsonConverter.toJSON(res); + } else if (fnName.equals("nonNullableDefault")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + String stringArg = null; + if (inputArgs.get("stringArg") != null) { + stringArg = (String) JsonConverter.fromJSON(inputArgs.get("stringArg"), String.class); + } + Objects.requireNonNull(stringArg, "stringArg must not be null"); + String res = obj.nonNullableDefault(stringArg); + return JsonConverter.toJSON(res); + } else if (fnName.equals("nullable")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + String stringArg = null; + if (inputArgs.get("stringArg") != null) { + stringArg = (String) JsonConverter.fromJSON(inputArgs.get("stringArg"), String.class); + } + var stringArg_opt = Optional.ofNullable(stringArg); + String res = obj.nullable(stringArg_opt); + return JsonConverter.toJSON(res); + } else if (fnName.equals("nullableDefault")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + String stringArg = null; + if (inputArgs.get("stringArg") != null) { + stringArg = (String) JsonConverter.fromJSON(inputArgs.get("stringArg"), String.class); + } + var stringArg_opt = Optional.ofNullable(stringArg); + String res = obj.nullableDefault(stringArg_opt); + return JsonConverter.toJSON(res); + } else if (fnName.equals("defaultPlatform")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + Platform res = obj.defaultPlatform(); + return JsonConverter.toJSON(res); + } else if (fnName.equals("addFloat")) { + Class clazz = Class.forName("io.dagger.java.module.DaggerJava"); + DaggerJava obj = (DaggerJava) JsonConverter.fromJSON(parentJson, clazz); + float a = 0; + if (inputArgs.get("a") != null) { + a = (float) JsonConverter.fromJSON(inputArgs.get("a"), float.class); + } + float b = 0; + if (inputArgs.get("b") != null) { + b = (float) JsonConverter.fromJSON(inputArgs.get("b"), float.class); + } + float res = obj.addFloat(a, b); + return JsonConverter.toJSON(res); + } if (fnName.equals("")) { + Directory source = null; + if (inputArgs.get("source") != null) { + source = (Directory) JsonConverter.fromJSON(inputArgs.get("source"), Directory.class); + } + var source_opt = Optional.ofNullable(source); + String version = null; + if (inputArgs.get("version") != null) { + version = (String) JsonConverter.fromJSON(inputArgs.get("version"), String.class); } + Objects.requireNonNull(version, "version must not be null"); + DaggerJava res = new DaggerJava(source_opt, version); + return JsonConverter.toJSON(res); } } throw new InvocationTargetException(new Error("unknown function " + fnName)); diff --git a/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/java/module/DaggerJava.java b/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/java/module/DaggerJava.java index 579c177d4eb..bbd6f797134 100644 --- a/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/java/module/DaggerJava.java +++ b/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/java/module/DaggerJava.java @@ -1,7 +1,8 @@ package io.dagger.java.module; +import static io.dagger.client.Dagger.dag; + import io.dagger.client.*; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.*; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @@ -11,17 +12,19 @@ /** Dagger Java Module main object */ @Object -public class DaggerJava extends AbstractModule { - private String notExportedField; +public class DaggerJava { + private transient String notExportedField; /** Project source directory */ public Directory source; - public String version; + // this field will also be exposed as a Dagger Field, even if private + @Function private String version; - public DaggerJava() { - super(); - } + // this field will be serialized but not exposed as a field + private Container container; + + public DaggerJava() {} /** * Initialize the DaggerJava Module @@ -29,9 +32,8 @@ public DaggerJava() { * @param source Project source directory * @param version Go version */ - public DaggerJava(Client dag, Optional source, @Default("1.23.2") String version) { - super(dag); - this.source = source.orElseGet(() -> dag.currentModule().source()); + public DaggerJava(Optional source, @Default("1.23.2") String version) { + this.source = source.orElseGet(() -> dag().currentModule().source()); this.version = version; } @@ -43,7 +45,7 @@ public DaggerJava(Client dag, Optional source, @Default("1.23.2") Str */ @Function public Container containerEcho(@Default("Hello Dagger") String stringArg) { - return dag.container().from("alpine:latest").withExec(List.of("echo", stringArg)); + return dag().container().from("alpine:latest").withExec(List.of("echo", stringArg)); } /** @@ -59,7 +61,8 @@ public String grepDir( Optional pattern) throws InterruptedException, ExecutionException, DaggerQueryException { String grepPattern = pattern.orElse("dagger"); - return dag.container() + return dag() + .container() .from("alpine:latest") .withMountedDirectory("/mnt", directoryArg) .withWorkdir("/mnt") @@ -127,7 +130,7 @@ public String nullableDefault(@Default("Foo") Optional stringArg) { @Function public Platform defaultPlatform() throws InterruptedException, ExecutionException, DaggerQueryException { - return dag.defaultPlatform(); + return dag().defaultPlatform(); } @Function diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/BuildFromDockerfile.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/BuildFromDockerfile.java index 5e516158fff..ecc18ee7b0d 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/BuildFromDockerfile.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/BuildFromDockerfile.java @@ -1,15 +1,12 @@ package io.dagger.sample; -import io.dagger.client.Client; -import io.dagger.client.Container; -import io.dagger.client.Dagger; -import io.dagger.client.Directory; +import io.dagger.client.*; import java.util.List; @Description("Clone the Dagger git repository and build from a Dockerfile") public class BuildFromDockerfile { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { Directory dir = client.git("https://github.com/dagger/dagger").tag("v0.6.2").tree(); Container daggerImg = client.container().build(dir); diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ContainerToHostNetworking.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ContainerToHostNetworking.java index edac7ec03db..9038879ecb1 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ContainerToHostNetworking.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ContainerToHostNetworking.java @@ -1,16 +1,13 @@ package io.dagger.sample; -import io.dagger.client.Client; -import io.dagger.client.Dagger; -import io.dagger.client.PortForward; -import io.dagger.client.Service; +import io.dagger.client.*; import java.util.List; @Description("Expose MySQL service running on the host to client containers") public class ContainerToHostNetworking { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { // expose host service on port 3306 Service hostSrv = client.host().service(List.of(new PortForward().withBackend(3306).withFrontend(3306))); diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/CreateAndUseSecret.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/CreateAndUseSecret.java index 33767b19921..ac453d27f15 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/CreateAndUseSecret.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/CreateAndUseSecret.java @@ -10,7 +10,7 @@ public static void main(String... args) throws Exception { if (token == null) { token = new String(System.console().readPassword("GithHub API token: ")); } - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { Secret secret = client.setSecret("ghApiToken", token); // use secret in container environment diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/GetDaggerWebsite.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/GetDaggerWebsite.java index f62d97e9c45..1eb743ea9f7 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/GetDaggerWebsite.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/GetDaggerWebsite.java @@ -1,13 +1,13 @@ package io.dagger.sample; -import io.dagger.client.Client; +import io.dagger.client.AutoCloseableClient; import io.dagger.client.Dagger; import java.util.List; @Description("Fetch the Dagger website content and print the first 300 characters") public class GetDaggerWebsite { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { String output = client .container() diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/HostToContainerNetworking.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/HostToContainerNetworking.java index 365eae04d05..95299fe4414 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/HostToContainerNetworking.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/HostToContainerNetworking.java @@ -1,6 +1,6 @@ package io.dagger.sample; -import io.dagger.client.Client; +import io.dagger.client.AutoCloseableClient; import io.dagger.client.Dagger; import io.dagger.client.Service; import java.net.URL; @@ -11,7 +11,7 @@ public class HostToContainerNetworking { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { // create web service container with exposed port 8080 Service httpSrv = client diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ListEnvVars.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ListEnvVars.java index eb1b86e923f..6545aad124c 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ListEnvVars.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ListEnvVars.java @@ -1,6 +1,6 @@ package io.dagger.sample; -import io.dagger.client.Client; +import io.dagger.client.AutoCloseableClient; import io.dagger.client.Dagger; import io.dagger.client.EnvVariable; import java.util.List; @@ -8,7 +8,7 @@ @Description("List container environment variables") public class ListEnvVars { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { List env = client.container().from("alpine").withEnvVariable("MY_VAR", "some_value").envVariables(); for (EnvVariable var : env) { diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ListHostDirectoryContents.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ListHostDirectoryContents.java index 897ab664ec8..ed924761f65 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ListHostDirectoryContents.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ListHostDirectoryContents.java @@ -1,13 +1,13 @@ package io.dagger.sample; -import io.dagger.client.Client; +import io.dagger.client.AutoCloseableClient; import io.dagger.client.Dagger; import java.util.List; @Description("List the files and directories from the host working dir in a container") public class ListHostDirectoryContents { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { List entries = client.host().directory(".").entries(); entries.stream().forEach(System.out::println); } diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/MountHostDirectoryInContainer.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/MountHostDirectoryInContainer.java index c125836e068..8e90445a031 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/MountHostDirectoryInContainer.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/MountHostDirectoryInContainer.java @@ -1,13 +1,13 @@ package io.dagger.sample; -import io.dagger.client.Client; +import io.dagger.client.AutoCloseableClient; import io.dagger.client.Dagger; import java.util.List; @Description("Mount a host directory in container") public class MountHostDirectoryInContainer { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { String contents = client .container() diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/PublishImage.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/PublishImage.java index 98ed279a81c..bfd7457b4ba 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/PublishImage.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/PublishImage.java @@ -1,12 +1,8 @@ package io.dagger.sample; -import io.dagger.client.Client; +import io.dagger.client.*; import io.dagger.client.Client.ContainerArguments; -import io.dagger.client.Container; import io.dagger.client.Container.WithNewFileArguments; -import io.dagger.client.Dagger; -import io.dagger.client.Platform; -import io.dagger.client.Secret; @Description("Publish a container image to a remote registry") public class PublishImage { @@ -20,7 +16,7 @@ public static void main(String... args) throws Exception { if (password == null) { password = new String(System.console().readPassword("Docker Hub password: ")); } - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { // set secret as string value Secret secret = client.setSecret("password", password); diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ReadFileInGitRepository.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ReadFileInGitRepository.java index 51323e52425..13db0d462de 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ReadFileInGitRepository.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/ReadFileInGitRepository.java @@ -1,6 +1,6 @@ package io.dagger.sample; -import io.dagger.client.Client; +import io.dagger.client.AutoCloseableClient; import io.dagger.client.Dagger; import java.io.BufferedReader; import java.io.StringReader; @@ -8,7 +8,7 @@ @Description("Clone the Dagger git repository and print the first line of README.md") public class ReadFileInGitRepository { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { String readme = client .git("https://github.com/dagger/dagger") diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/RunContainer.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/RunContainer.java index 8ed82b526a1..5a0076c5a29 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/RunContainer.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/RunContainer.java @@ -1,6 +1,6 @@ package io.dagger.sample; -import io.dagger.client.Client; +import io.dagger.client.AutoCloseableClient; import io.dagger.client.Container; import io.dagger.client.Dagger; import java.util.List; @@ -8,7 +8,7 @@ @Description("Run a binary in a container") public class RunContainer { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { Container container = client.container().from("maven:3.9.2").withExec(List.of("mvn", "--version")); diff --git a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/TestWithDatabase.java b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/TestWithDatabase.java index fc4e8524dc0..c6c52fde2de 100644 --- a/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/TestWithDatabase.java +++ b/sdk/java/dagger-java-samples/src/main/java/io/dagger/sample/TestWithDatabase.java @@ -1,15 +1,12 @@ package io.dagger.sample; -import io.dagger.client.Client; -import io.dagger.client.Container; -import io.dagger.client.Dagger; -import io.dagger.client.Service; +import io.dagger.client.*; import java.util.List; @Description("Run a sample CI test pipeline with MariaDB, Drupal and PHPUnit") public class TestWithDatabase { public static void main(String... args) throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { // get MariaDB base image Service mariadb = diff --git a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/AutoCloseableClient.java b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/AutoCloseableClient.java new file mode 100644 index 00000000000..823f0e1c336 --- /dev/null +++ b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/AutoCloseableClient.java @@ -0,0 +1,13 @@ +package io.dagger.client; + +import io.dagger.client.engineconn.Connection; + +public class AutoCloseableClient extends Client implements AutoCloseable { + AutoCloseableClient(Connection connection) { + super(connection); + } + + AutoCloseableClient(QueryBuilder queryBuilder) { + super(queryBuilder); + } +} diff --git a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/Dagger.java b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/Dagger.java index d20ed28880c..c994e80c8aa 100644 --- a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/Dagger.java +++ b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/Dagger.java @@ -4,6 +4,26 @@ import java.io.IOException; public class Dagger { + private static Client dag = null; + + /** + * Returns the global Dagger client instance. + * + *

    Contrary to {@code connect}, this is managed as a singleton. It will always return the same + * instance. + * + * @return Global Dagger client + */ + public static Client dag() { + if (dag == null) { + try { + dag = new Client(Connection.get(System.getProperty("user.dir"))); + } catch (IOException e) { + throw new RuntimeException("Could not connect to Dagger engine", e); + } + } + return dag; + } /** * Opens connection with a Dagger engine. @@ -11,7 +31,7 @@ public class Dagger { * @return The Dagger API entrypoint * @throws IOException */ - public static Client connect() throws IOException { + public static AutoCloseableClient connect() throws IOException { return connect(System.getProperty("user.dir")); } @@ -22,7 +42,7 @@ public static Client connect() throws IOException { * @return The Dagger API entrypoint * @throws IOException */ - public static Client connect(String workingDir) throws IOException { - return new Client(Connection.get(workingDir)); + public static AutoCloseableClient connect(String workingDir) throws IOException { + return new AutoCloseableClient(Connection.get(workingDir)); } } diff --git a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/FieldsStrategy.java b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/FieldsStrategy.java new file mode 100644 index 00000000000..00e9516e3a6 --- /dev/null +++ b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/client/FieldsStrategy.java @@ -0,0 +1,18 @@ +package io.dagger.client; + +import jakarta.json.bind.config.PropertyVisibilityStrategy; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +public class FieldsStrategy implements PropertyVisibilityStrategy { + @Override + public boolean isVisible(Field field) { + return !Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()); + } + + @Override + public boolean isVisible(Method method) { + return false; + } +} diff --git a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/AbstractModule.java b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/AbstractModule.java deleted file mode 100644 index 9b56e112cc8..00000000000 --- a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/AbstractModule.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.dagger.module; - -import io.dagger.client.Client; - -public abstract class AbstractModule { - protected transient Client dag; - - public void setClient(Client dag) { - this.dag = dag; - } - - public AbstractModule(Client dag) { - this.dag = dag; - } - - public AbstractModule() {} -} diff --git a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/annotation/Function.java b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/annotation/Function.java index cbc6cd000a8..4d81a91fbdf 100644 --- a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/annotation/Function.java +++ b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/annotation/Function.java @@ -5,7 +5,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.SOURCE) public @interface Function { String value() default ""; diff --git a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/info/ConstructorInfo.java b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/info/ConstructorInfo.java deleted file mode 100644 index 40e3c6e617f..00000000000 --- a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/info/ConstructorInfo.java +++ /dev/null @@ -1,3 +0,0 @@ -package io.dagger.module.info; - -public record ConstructorInfo(boolean hasDaggerClient, FunctionInfo constructor) {} diff --git a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/info/ObjectInfo.java b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/info/ObjectInfo.java index 640a295d061..d0f83dff89d 100644 --- a/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/info/ObjectInfo.java +++ b/sdk/java/dagger-java-sdk/src/main/java/io/dagger/module/info/ObjectInfo.java @@ -8,4 +8,4 @@ public record ObjectInfo( String description, FieldInfo[] fields, FunctionInfo[] functions, - Optional constructor) {} + Optional constructor) {} diff --git a/sdk/java/dagger-java-sdk/src/test/java/io/dagger/client/ClientIT.java b/sdk/java/dagger-java-sdk/src/test/java/io/dagger/client/ClientIT.java index 316e6d5b2ff..6e0829bebb7 100644 --- a/sdk/java/dagger-java-sdk/src/test/java/io/dagger/client/ClientIT.java +++ b/sdk/java/dagger-java-sdk/src/test/java/io/dagger/client/ClientIT.java @@ -17,7 +17,7 @@ public static void setLoggerConfig() { @Test public void testDirectory() throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { Directory dir = client.directory(); String content = dir.withNewFile("/hello.txt", "world").file("/hello.txt").contents(); assertEquals("world", content); @@ -26,7 +26,7 @@ public void testDirectory() throws Exception { @Test public void testGit() throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { Directory tree = client.git("github.com/dagger/dagger").branch("main").tree(); List files = tree.entries(); assertTrue(files.contains("README.md")); @@ -45,7 +45,7 @@ public void testGit() throws Exception { @Test public void testContainer() throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { Container alpine = client.container().from("alpine:3.16.2"); String contents = alpine.rootfs().file("/etc/alpine-release").contents(); assertEquals("3.16.2\n", contents); @@ -62,7 +62,7 @@ public void testContainer() throws Exception { @Test public void testError() throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { try { client.container().from("fake.invalid:latest").id(); } catch (DaggerQueryException dqe) { @@ -80,7 +80,7 @@ public void testError() throws Exception { @Test public void testList() throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { List envs = client .container() diff --git a/sdk/java/dagger-java-sdk/src/test/java/io/dagger/client/ConvertTest.java b/sdk/java/dagger-java-sdk/src/test/java/io/dagger/client/ConvertTest.java index c35e28d1704..434659d8595 100644 --- a/sdk/java/dagger-java-sdk/src/test/java/io/dagger/client/ConvertTest.java +++ b/sdk/java/dagger-java-sdk/src/test/java/io/dagger/client/ConvertTest.java @@ -7,13 +7,13 @@ public class ConvertTest { @Test public void json_convert() throws Exception { - try (Client client = Dagger.connect()) { + try (AutoCloseableClient client = Dagger.connect()) { Container alpine = client.container().from("alpine:3.16.2").withWorkdir("/my-dir"); assertThat(alpine.workdir()).isEqualTo("/my-dir"); JSON json = JsonConverter.toJSON(alpine); - Container a = JsonConverter.fromJSON(client, json, Container.class); + Container a = JsonConverter.fromJSON(json, Container.class); assertThat(a.workdir()).isEqualTo("/my-dir"); } } diff --git a/sdk/java/runtime/main.go b/sdk/java/runtime/main.go index 166d7c4988a..21c46488d53 100644 --- a/sdk/java/runtime/main.go +++ b/sdk/java/runtime/main.go @@ -4,6 +4,7 @@ package main import ( "context" + "encoding/json" "fmt" "path/filepath" "strings" @@ -100,7 +101,26 @@ func (m *JavaSdk) codegenBase( // Set the working directory to the one containing the sources to build, not just the module root WithWorkdir(m.moduleConfig.modulePath()) // Add a default template if there's no existing user code - return m.addTemplate(ctx, ctr) + ctr, err = m.addTemplate(ctx, ctr) + if err != nil { + return nil, err + } + // Ensure the version in the pom.xml is the same as the introspection file + // This is updating the pom.xml whatever it's coming from the template or the user module + version, err := m.getDaggerVersionForModule(ctx, introspectionJSON) + if err != nil { + return nil, err + } + ctr = ctr. + // set the version of the Dagger dependencies to the version of the introspection file + WithExec([]string{ + "mvn", + "versions:set-property", + "-DgenerateBackupPoms=false", + "-Dproperty=dagger.module.deps", + fmt.Sprintf("-DnewVersion=%s", version), + }) + return ctr, nil } // buildJavaDependencies builds and install the needed dependencies @@ -115,6 +135,10 @@ func (m *JavaSdk) buildJavaDependencies( if err != nil { return nil, err } + version, err := m.getDaggerVersionForModule(ctx, introspectionJSON) + if err != nil { + return nil, err + } return ctr. // Cache maven dependencies WithMountedCache("/root/.m2", dag.CacheVolume("sdk-java-maven-m2")). @@ -123,6 +147,13 @@ func (m *JavaSdk) buildJavaDependencies( // Copy the SDK source directory, so all the files needed to build the dependencies WithDirectory(GenPath, m.SDKSourceDir). WithWorkdir(GenPath). + // Set the version of the dependencies we are building to the version of the introspection file + WithExec([]string{ + "mvn", + "versions:set", + "-DgenerateBackupPoms=false", + fmt.Sprintf("-DnewVersion=%s", version), + }). // Build and install the java modules one by one // - dagger-codegen-maven-plugin: this plugin will be used to generate the SDK code, from the introspection file, // this means including the ability to call other projects (not part of the main dagger SDK) @@ -164,8 +195,8 @@ func (m *JavaSdk) addTemplate( } changes := []repl{ - {"dagger-module", kebabName}, - {"daggermodule", pkgName}, + {"dagger-module-placeholder", kebabName}, + {"daggermoduleplaceholder", pkgName}, } // Edit template content so that they match the dagger module name @@ -215,7 +246,12 @@ func (m *JavaSdk) generateCode( // set the module name as an environment variable so we ensure constructor is only on main object WithEnvVariable("_DAGGER_JAVA_SDK_MODULE_NAME", m.moduleConfig.name). // generate the entrypoint - WithExec([]string{"mvn", "clean", "compile"}) + WithExec([]string{ + "mvn", + "clean", + "compile", + // "-e", // this is just for debug purpose, uncomment if needed + }) return dag. Directory(). // copy all user files @@ -352,6 +388,26 @@ func (m *JavaSdk) setModuleConfig(ctx context.Context, modSource *dagger.ModuleS return nil } +func (m *JavaSdk) getDaggerVersionForModule(ctx context.Context, introspectionJSON *dagger.File) (string, error) { + content, err := introspectionJSON.Contents(ctx) + if err != nil { + return "", err + } + var introspectJSON IntrospectJSON + if err = json.Unmarshal([]byte(content), &introspectJSON); err != nil { + return "", err + } + return fmt.Sprintf( + "%s-%s-module", + strings.TrimPrefix(introspectJSON.SchemaVersion, "v"), + m.moduleConfig.name, + ), nil +} + +type IntrospectJSON struct { + SchemaVersion string `json:"__schemaVersion"` +} + type repl struct { oldString string newString string diff --git a/sdk/java/runtime/template/pom.xml b/sdk/java/runtime/template/pom.xml index 92db57221ac..08b4d0c70a9 100644 --- a/sdk/java/runtime/template/pom.xml +++ b/sdk/java/runtime/template/pom.xml @@ -5,25 +5,26 @@ 4.0.0 io.dagger.modules.daggermodule - dagger-module + dagger-module-placeholder 1.0-SNAPSHOT - dagger-module + dagger-module-placeholder 17 UTF-8 + 0.16.2-template-module io.dagger dagger-java-sdk - 1.0.0-SNAPSHOT + ${dagger.module.deps} io.dagger dagger-java-annotation-processor - 1.0.0-SNAPSHOT + ${dagger.module.deps} provided @@ -75,7 +76,7 @@ io.dagger dagger-java-annotation-processor - 1.0.0-SNAPSHOT + ${dagger.module.deps} diff --git a/sdk/java/runtime/template/src/main/java/io/dagger/modules/daggermodule/DaggerModule.java b/sdk/java/runtime/template/src/main/java/io/dagger/modules/daggermodule/DaggerModule.java index 06121e3967a..8c08a222b8d 100644 --- a/sdk/java/runtime/template/src/main/java/io/dagger/modules/daggermodule/DaggerModule.java +++ b/sdk/java/runtime/template/src/main/java/io/dagger/modules/daggermodule/DaggerModule.java @@ -1,9 +1,10 @@ -package io.dagger.modules.daggermodule; +package io.dagger.modules.daggermoduleplaceholder; + +import static io.dagger.client.Dagger.dag; import io.dagger.client.Container; import io.dagger.client.DaggerQueryException; import io.dagger.client.Directory; -import io.dagger.module.AbstractModule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @@ -11,18 +12,19 @@ /** DaggerModule main object */ @Object -public class DaggerModule extends AbstractModule { +public class DaggerModule { /** Returns a container that echoes whatever string argument is provided */ @Function public Container containerEcho(String stringArg) { - return dag.container().from("alpine:latest").withExec(List.of("echo", stringArg)); + return dag().container().from("alpine:latest").withExec(List.of("echo", stringArg)); } /** Returns lines that match a pattern in the files of the provided Directory */ @Function public String grepDir(Directory directoryArg, String pattern) throws InterruptedException, ExecutionException, DaggerQueryException { - return dag.container() + return dag() + .container() .from("alpine:latest") .withMountedDirectory("/mnt", directoryArg) .withWorkdir("/mnt") diff --git a/sdk/java/runtime/template/src/main/java/io/dagger/modules/daggermodule/package-info.java b/sdk/java/runtime/template/src/main/java/io/dagger/modules/daggermodule/package-info.java index cdadf7640c6..1820dd51c9f 100644 --- a/sdk/java/runtime/template/src/main/java/io/dagger/modules/daggermodule/package-info.java +++ b/sdk/java/runtime/template/src/main/java/io/dagger/modules/daggermodule/package-info.java @@ -1,5 +1,5 @@ /** DaggerModule example */ @Module -package io.dagger.modules.daggermodule; +package io.dagger.modules.daggermoduleplaceholder; import io.dagger.module.annotation.Module; diff --git a/sdk/php/generated/Client.php b/sdk/php/generated/Client.php index c6b3108a4f7..1578e9d5c91 100644 --- a/sdk/php/generated/Client.php +++ b/sdk/php/generated/Client.php @@ -447,6 +447,16 @@ public function loadListTypeDefFromID(ListTypeDefId|ListTypeDef $id): ListTypeDe return new \Dagger\ListTypeDef($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); } + /** + * Load a ModuleConfigClient from its ID. + */ + public function loadModuleConfigClientFromID(ModuleConfigClientId|ModuleConfigClient $id): ModuleConfigClient + { + $innerQueryBuilder = new \Dagger\Client\QueryBuilder('loadModuleConfigClientFromID'); + $innerQueryBuilder->setArgument('id', $id); + return new \Dagger\ModuleConfigClient($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); + } + /** * Load a Module from its ID. */ diff --git a/sdk/php/generated/ModuleConfigClient.php b/sdk/php/generated/ModuleConfigClient.php new file mode 100644 index 00000000000..58076b0c68e --- /dev/null +++ b/sdk/php/generated/ModuleConfigClient.php @@ -0,0 +1,51 @@ +queryLeaf($leafQueryBuilder, 'dev'); + } + + /** + * The directory the client is generated in. + */ + public function directory(): string + { + $leafQueryBuilder = new \Dagger\Client\QueryBuilder('directory'); + return (string)$this->queryLeaf($leafQueryBuilder, 'directory'); + } + + /** + * The generator to use + */ + public function generator(): string + { + $leafQueryBuilder = new \Dagger\Client\QueryBuilder('generator'); + return (string)$this->queryLeaf($leafQueryBuilder, 'generator'); + } + + /** + * A unique identifier for this ModuleConfigClient. + */ + public function id(): ModuleConfigClientId + { + $leafQueryBuilder = new \Dagger\Client\QueryBuilder('id'); + return new \Dagger\ModuleConfigClientId((string)$this->queryLeaf($leafQueryBuilder, 'id')); + } +} diff --git a/sdk/php/generated/ModuleConfigClientId.php b/sdk/php/generated/ModuleConfigClientId.php new file mode 100644 index 00000000000..11c6a637221 --- /dev/null +++ b/sdk/php/generated/ModuleConfigClientId.php @@ -0,0 +1,16 @@ +queryLeaf($leafQueryBuilder, 'commit'); } + /** + * The clients generated for the module. + */ + public function configClients(): array + { + $leafQueryBuilder = new \Dagger\Client\QueryBuilder('configClients'); + return (array)$this->queryLeaf($leafQueryBuilder, 'configClients'); + } + /** * Whether an existing dagger.json for the module was found. */ @@ -104,20 +113,6 @@ public function engineVersion(): string return (string)$this->queryLeaf($leafQueryBuilder, 'engineVersion'); } - /** - * Generates a client for the module. - */ - public function generateClient(string $generator, string $outputDir, ?bool $localSdk = null): Directory - { - $innerQueryBuilder = new \Dagger\Client\QueryBuilder('generateClient'); - $innerQueryBuilder->setArgument('generator', $generator); - $innerQueryBuilder->setArgument('outputDir', $outputDir); - if (null !== $localSdk) { - $innerQueryBuilder->setArgument('localSdk', $localSdk); - } - return new \Dagger\Directory($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); - } - /** * The generated files and directories made on top of the module source's context directory. */ @@ -262,6 +257,20 @@ public function version(): string return (string)$this->queryLeaf($leafQueryBuilder, 'version'); } + /** + * Update the module source with a new client to generate. + */ + public function withClient(string $generator, string $outputDir, ?bool $dev = null): ModuleSource + { + $innerQueryBuilder = new \Dagger\Client\QueryBuilder('withClient'); + $innerQueryBuilder->setArgument('generator', $generator); + $innerQueryBuilder->setArgument('outputDir', $outputDir); + if (null !== $dev) { + $innerQueryBuilder->setArgument('dev', $dev); + } + return new \Dagger\ModuleSource($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); + } + /** * Append the provided dependencies to the module source's dependency list. */ diff --git a/sdk/python/src/dagger/client/gen.py b/sdk/python/src/dagger/client/gen.py index 9513b210dd5..a0ad6df12b8 100644 --- a/sdk/python/src/dagger/client/gen.py +++ b/sdk/python/src/dagger/client/gen.py @@ -145,6 +145,11 @@ class ListTypeDefID(Scalar): object of type ListTypeDef.""" +class ModuleConfigClientID(Scalar): + """The `ModuleConfigClientID` scalar type represents an identifier for + an object of type ModuleConfigClient.""" + + class ModuleID(Scalar): """The `ModuleID` scalar type represents an identifier for an object of type Module.""" @@ -5666,6 +5671,96 @@ def with_(self, cb: Callable[["Module"], "Module"]) -> "Module": return cb(self) +@typecheck +class ModuleConfigClient(Type): + """The client generated for the module.""" + + async def dev(self) -> bool | None: + """If true, generate the client in developer mode. + + Returns + ------- + bool | None + The `Boolean` scalar type represents `true` or `false`. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("dev", _args) + return await _ctx.execute(bool | None) + + async def directory(self) -> str: + """The directory the client is generated in. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("directory", _args) + return await _ctx.execute(str) + + async def generator(self) -> str: + """The generator to use + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("generator", _args) + return await _ctx.execute(str) + + async def id(self) -> ModuleConfigClientID: + """A unique identifier for this ModuleConfigClient. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ModuleConfigClientID + The `ModuleConfigClientID` scalar type represents an identifier + for an object of type ModuleConfigClient. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ModuleConfigClientID) + + @typecheck class ModuleSource(Type): """The source needed to load and run a module, along with any metadata @@ -5744,6 +5839,27 @@ async def commit(self) -> str: _ctx = self._select("commit", _args) return await _ctx.execute(str) + async def config_clients(self) -> list[ModuleConfigClient]: + """The clients generated for the module.""" + _args: list[Arg] = [] + _ctx = self._select("configClients", _args) + _ctx = ModuleConfigClient(_ctx)._select("id", []) + + @dataclass + class Response: + id: ModuleConfigClientID + + _ids = await _ctx.execute(list[Response]) + return [ + ModuleConfigClient( + Client.from_context(_ctx)._select( + "loadModuleConfigClientFromID", + [Arg("id", v.id)], + ) + ) + for v in _ids + ] + async def config_exists(self) -> bool: """Whether an existing dagger.json for the module was found. @@ -5851,32 +5967,6 @@ async def engine_version(self) -> str: _ctx = self._select("engineVersion", _args) return await _ctx.execute(str) - def generate_client( - self, - generator: str, - output_dir: str, - *, - local_sdk: bool | None = None, - ) -> Directory: - """Generates a client for the module. - - Parameters - ---------- - generator: - The generator to use - output_dir: - The output directory for the generated client. - local_sdk: - Use local SDK dependency - """ - _args = [ - Arg("generator", generator), - Arg("outputDir", output_dir), - Arg("localSdk", local_sdk, None), - ] - _ctx = self._select("generateClient", _args) - return Directory(_ctx) - def generated_context_directory(self) -> Directory: """The generated files and directories made on top of the module source's context directory. @@ -6197,6 +6287,32 @@ async def version(self) -> str: _ctx = self._select("version", _args) return await _ctx.execute(str) + def with_client( + self, + generator: str, + output_dir: str, + *, + dev: bool | None = None, + ) -> Self: + """Update the module source with a new client to generate. + + Parameters + ---------- + generator: + The generator to use + output_dir: + The output directory for the generated client. + dev: + Generate in developer mode + """ + _args = [ + Arg("generator", generator), + Arg("outputDir", output_dir), + Arg("dev", dev, None), + ] + _ctx = self._select("withClient", _args) + return ModuleSource(_ctx) + def with_dependencies(self, dependencies: list["ModuleSource"]) -> Self: """Append the provided dependencies to the module source's dependency list. @@ -7037,6 +7153,16 @@ def load_list_type_def_from_id(self, id: ListTypeDefID) -> ListTypeDef: _ctx = self._select("loadListTypeDefFromID", _args) return ListTypeDef(_ctx) + def load_module_config_client_from_id( + self, id: ModuleConfigClientID + ) -> ModuleConfigClient: + """Load a ModuleConfigClient from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadModuleConfigClientFromID", _args) + return ModuleConfigClient(_ctx) + def load_module_from_id(self, id: ModuleID) -> Module: """Load a Module from its ID.""" _args = [ @@ -8292,6 +8418,8 @@ def with_(self, cb: Callable[["TypeDef"], "TypeDef"]) -> "TypeDef": "ListTypeDef", "ListTypeDefID", "Module", + "ModuleConfigClient", + "ModuleConfigClientID", "ModuleID", "ModuleSource", "ModuleSourceID", diff --git a/sdk/rust/crates/dagger-sdk/src/gen.rs b/sdk/rust/crates/dagger-sdk/src/gen.rs index 86b154ec61d..e634d1150c8 100644 --- a/sdk/rust/crates/dagger-sdk/src/gen.rs +++ b/sdk/rust/crates/dagger-sdk/src/gen.rs @@ -923,6 +923,41 @@ impl ListTypeDefId { } } #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct ModuleConfigClientId(pub String); +impl From<&str> for ModuleConfigClientId { + fn from(value: &str) -> Self { + Self(value.to_string()) + } +} +impl From for ModuleConfigClientId { + fn from(value: String) -> Self { + Self(value) + } +} +impl IntoID for ModuleConfigClient { + fn into_id( + self, + ) -> std::pin::Pin< + Box> + Send>, + > { + Box::pin(async move { self.id().await }) + } +} +impl IntoID for ModuleConfigClientId { + fn into_id( + self, + ) -> std::pin::Pin< + Box> + Send>, + > { + Box::pin(async move { Ok::(self) }) + } +} +impl ModuleConfigClientId { + fn quote(&self) -> String { + format!("\"{}\"", self.0.clone()) + } +} +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] pub struct ModuleId(pub String); impl From<&str> for ModuleId { fn from(value: &str) -> Self { @@ -6085,16 +6120,44 @@ impl Module { } } #[derive(Clone)] +pub struct ModuleConfigClient { + pub proc: Option>, + pub selection: Selection, + pub graphql_client: DynGraphQLClient, +} +impl ModuleConfigClient { + /// If true, generate the client in developer mode. + pub async fn dev(&self) -> Result { + let query = self.selection.select("dev"); + query.execute(self.graphql_client.clone()).await + } + /// The directory the client is generated in. + pub async fn directory(&self) -> Result { + let query = self.selection.select("directory"); + query.execute(self.graphql_client.clone()).await + } + /// The generator to use + pub async fn generator(&self) -> Result { + let query = self.selection.select("generator"); + query.execute(self.graphql_client.clone()).await + } + /// A unique identifier for this ModuleConfigClient. + pub async fn id(&self) -> Result { + let query = self.selection.select("id"); + query.execute(self.graphql_client.clone()).await + } +} +#[derive(Clone)] pub struct ModuleSource { pub proc: Option>, pub selection: Selection, pub graphql_client: DynGraphQLClient, } #[derive(Builder, Debug, PartialEq)] -pub struct ModuleSourceGenerateClientOpts { - /// Use local SDK dependency +pub struct ModuleSourceWithClientOpts { + /// Generate in developer mode #[builder(setter(into, strip_option), default)] - pub local_sdk: Option, + pub dev: Option, } impl ModuleSource { /// Load the source as a module. If this is a local source, the parent directory must have been provided during module source creation @@ -6121,6 +6184,15 @@ impl ModuleSource { let query = self.selection.select("commit"); query.execute(self.graphql_client.clone()).await } + /// The clients generated for the module. + pub fn config_clients(&self) -> Vec { + let query = self.selection.select("configClients"); + vec![ModuleConfigClient { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + }] + } /// Whether an existing dagger.json for the module was found. pub async fn config_exists(&self) -> Result { let query = self.selection.select("configExists"); @@ -6168,52 +6240,6 @@ impl ModuleSource { let query = self.selection.select("engineVersion"); query.execute(self.graphql_client.clone()).await } - /// Generates a client for the module. - /// - /// # Arguments - /// - /// * `generator` - The generator to use - /// * `output_dir` - The output directory for the generated client. - /// * `opt` - optional argument, see inner type for documentation, use _opts to use - pub fn generate_client( - &self, - generator: impl Into, - output_dir: impl Into, - ) -> Directory { - let mut query = self.selection.select("generateClient"); - query = query.arg("generator", generator.into()); - query = query.arg("outputDir", output_dir.into()); - Directory { - proc: self.proc.clone(), - selection: query, - graphql_client: self.graphql_client.clone(), - } - } - /// Generates a client for the module. - /// - /// # Arguments - /// - /// * `generator` - The generator to use - /// * `output_dir` - The output directory for the generated client. - /// * `opt` - optional argument, see inner type for documentation, use _opts to use - pub fn generate_client_opts( - &self, - generator: impl Into, - output_dir: impl Into, - opts: ModuleSourceGenerateClientOpts, - ) -> Directory { - let mut query = self.selection.select("generateClient"); - query = query.arg("generator", generator.into()); - query = query.arg("outputDir", output_dir.into()); - if let Some(local_sdk) = opts.local_sdk { - query = query.arg("localSdk", local_sdk); - } - Directory { - proc: self.proc.clone(), - selection: query, - graphql_client: self.graphql_client.clone(), - } - } /// The generated files and directories made on top of the module source's context directory. pub fn generated_context_directory(&self) -> Directory { let query = self.selection.select("generatedContextDirectory"); @@ -6302,6 +6328,52 @@ impl ModuleSource { let query = self.selection.select("version"); query.execute(self.graphql_client.clone()).await } + /// Update the module source with a new client to generate. + /// + /// # Arguments + /// + /// * `generator` - The generator to use + /// * `output_dir` - The output directory for the generated client. + /// * `opt` - optional argument, see inner type for documentation, use _opts to use + pub fn with_client( + &self, + generator: impl Into, + output_dir: impl Into, + ) -> ModuleSource { + let mut query = self.selection.select("withClient"); + query = query.arg("generator", generator.into()); + query = query.arg("outputDir", output_dir.into()); + ModuleSource { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } + /// Update the module source with a new client to generate. + /// + /// # Arguments + /// + /// * `generator` - The generator to use + /// * `output_dir` - The output directory for the generated client. + /// * `opt` - optional argument, see inner type for documentation, use _opts to use + pub fn with_client_opts( + &self, + generator: impl Into, + output_dir: impl Into, + opts: ModuleSourceWithClientOpts, + ) -> ModuleSource { + let mut query = self.selection.select("withClient"); + query = query.arg("generator", generator.into()); + query = query.arg("outputDir", output_dir.into()); + if let Some(dev) = opts.dev { + query = query.arg("dev", dev); + } + ModuleSource { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } /// Append the provided dependencies to the module source's dependency list. /// /// # Arguments @@ -7301,6 +7373,25 @@ impl Query { graphql_client: self.graphql_client.clone(), } } + /// Load a ModuleConfigClient from its ID. + pub fn load_module_config_client_from_id( + &self, + id: impl IntoID, + ) -> ModuleConfigClient { + let mut query = self.selection.select("loadModuleConfigClientFromID"); + query = query.arg_lazy( + "id", + Box::new(move || { + let id = id.clone(); + Box::pin(async move { id.into_id().await.unwrap().quote() }) + }), + ); + ModuleConfigClient { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } /// Load a Module from its ID. pub fn load_module_from_id(&self, id: impl IntoID) -> Module { let mut query = self.selection.select("loadModuleFromID"); diff --git a/sdk/typescript/runtime/bin/__tsclientconfig.updator.ts b/sdk/typescript/runtime/bin/__tsclientconfig.updator.ts index 6f1efc3e29d..add943592da 100644 --- a/sdk/typescript/runtime/bin/__tsclientconfig.updator.ts +++ b/sdk/typescript/runtime/bin/__tsclientconfig.updator.ts @@ -7,7 +7,7 @@ const daggerPath = "./sdk/src" const daggerTelemetryPath = "./sdk/src/telemetry" const daggerClientPathAlias = "@dagger.io/client" -const help = `Usage: ts_client_config_updator ` +const help = `Usage: ts_client_config_updator ` const args = process.argv.slice(2) class Arg { @@ -17,24 +17,24 @@ class Arg { ) {} } -const localSDK = new Arg("local-sdk", false) +const dev = new Arg("dev", false) const libraryDir = new Arg("library-dir", null) for (const arg of args) { const [name, value] = arg.slice("--".length).split("=") switch (name) { - case "local-sdk": + case "dev": if (value === undefined || value === "true") { - localSDK.value = true + dev.value = true break } if (value === "false") { - localSDK.value = false + dev.value = false break } - console.error(`Invalid value for local-sdk: ${value}\n ${help}`) + console.error(`Invalid value for dev: ${value}\n ${help}`) process.exit(1) break @@ -50,7 +50,7 @@ if (libraryDir.value === null) { } console.log( - `Updating ts client configuration (localSDK=${localSDK.value}) (libraryDir=${libraryDir.value})`, + `Updating ts client configuration (dev=${dev.value}) (libraryDir=${libraryDir.value})`, ) const tsConfigPath = `./tsconfig.json` @@ -74,7 +74,7 @@ if (!fs.existsSync(tsConfigPath)) { }, } - if (localSDK.value === true) { + if (dev.value === true) { defaultTsConfig.compilerOptions.paths[daggerPathAlias] = [daggerPath] defaultTsConfig.compilerOptions.paths[daggerTelemetryPathAlias] = [ daggerTelemetryPath, @@ -117,7 +117,7 @@ tsconfig.compilerOptions.paths[daggerClientPathAlias] = [ `./${path.join(libraryDir.value, "client.gen.ts")}`, ] -if (localSDK.value === true) { +if (dev.value === true) { tsconfig.compilerOptions.paths[daggerPathAlias] = [daggerPath] tsconfig.compilerOptions.paths[daggerTelemetryPathAlias] = [ daggerTelemetryPath, diff --git a/sdk/typescript/runtime/main.go b/sdk/typescript/runtime/main.go index 5c33d45eb91..2b34dcabc76 100644 --- a/sdk/typescript/runtime/main.go +++ b/sdk/typescript/runtime/main.go @@ -173,7 +173,7 @@ func (t *TypescriptSdk) GenerateClient( modSource *dagger.ModuleSource, introspectionJSON *dagger.File, outputDir string, - useLocalSdk bool, + dev bool, ) (*dagger.Directory, error) { workdirPath := "/module" @@ -182,8 +182,6 @@ func (t *TypescriptSdk) GenerateClient( return nil, fmt.Errorf("failed to get module source root subpath: %w", err) } - curentModuleDirectory := modSource.ContextDirectory().Directory(currentModuleDirectoryPath) - ctr := dag.Container(). From(tsdistconsts.DefaultNodeImageRef). WithoutEntrypoint(). @@ -197,36 +195,35 @@ func (t *TypescriptSdk) GenerateClient( WithExec([]string{"ln", "-s", "/usr/local/lib/node_modules/tsx/dist/cli.mjs", "/usr/local/bin/tsx"}). // Add dagger codegen binary. WithMountedFile(codegenBinPath, t.SDKSourceDir.File("/codegen")). - WithDirectory("/ctx", modSource.ContextDirectory()). // Mount the introspection file. WithMountedFile(schemaPath, introspectionJSON). // Mount the current module directory. - WithDirectory(workdirPath, curentModuleDirectory). - WithWorkdir(workdirPath). + WithDirectory(workdirPath, modSource.ContextDirectory()). + WithWorkdir(filepath.Join(workdirPath, currentModuleDirectoryPath)). // Execute the code generator using the given introspection file. WithExec([]string{ codegenBinPath, "--lang", "typescript", "--output", outputDir, "--introspection-json-path", schemaPath, - fmt.Sprintf("--local-sdk=%t", useLocalSdk), + fmt.Sprintf("--dev=%t", dev), "--client-only", }, dagger.ContainerWithExecOpts{ ExperimentalPrivilegedNesting: true, }) - if useLocalSdk { + if dev { ctr = ctr.WithDirectory("./sdk", t.SDKSourceDir. WithoutDirectory("codegen"). WithoutDirectory("runtime"). WithoutDirectory("tsx_module"), ). WithExec([]string{"npm", "pkg", "set", "dependencies[@dagger.io/dagger]=./sdk"}). - WithExec([]string{"tsx", "/opt/__tsclientconfig.updator.ts", "--local-sdk=true", fmt.Sprintf("--library-dir=%s", outputDir)}) + WithExec([]string{"tsx", "/opt/__tsclientconfig.updator.ts", "--dev=true", fmt.Sprintf("--library-dir=%s", outputDir)}) } else { ctr = ctr. WithExec([]string{"npm", "pkg", "set", "dependencies[@dagger.io/dagger]=@dagger.io/dagger"}). - WithExec([]string{"tsx", "/opt/__tsclientconfig.updator.ts", "--local-sdk=false", fmt.Sprintf("--library-dir=%s", outputDir)}) + WithExec([]string{"tsx", "/opt/__tsclientconfig.updator.ts", "--dev=false", fmt.Sprintf("--library-dir=%s", outputDir)}) } return dag.Directory().WithDirectory("/", ctr.Directory(workdirPath)), nil diff --git a/sdk/typescript/src/api/client.gen.ts b/sdk/typescript/src/api/client.gen.ts index 9dde75772f7..0f61c082493 100644 --- a/sdk/typescript/src/api/client.gen.ts +++ b/sdk/typescript/src/api/client.gen.ts @@ -1021,16 +1021,21 @@ export type LabelID = string & { __LabelID: never } */ export type ListTypeDefID = string & { __ListTypeDefID: never } +/** + * The `ModuleConfigClientID` scalar type represents an identifier for an object of type ModuleConfigClient. + */ +export type ModuleConfigClientID = string & { __ModuleConfigClientID: never } + /** * The `ModuleID` scalar type represents an identifier for an object of type Module. */ export type ModuleID = string & { __ModuleID: never } -export type ModuleSourceGenerateClientOpts = { +export type ModuleSourceWithClientOpts = { /** - * Use local SDK dependency + * Generate in developer mode */ - localSdk?: boolean + dev?: boolean } /** @@ -5329,6 +5334,94 @@ export class Module_ extends BaseClient { } } +/** + * The client generated for the module. + */ +export class ModuleConfigClient extends BaseClient { + private readonly _id?: ModuleConfigClientID = undefined + private readonly _dev?: boolean = undefined + private readonly _directory?: string = undefined + private readonly _generator?: string = undefined + + /** + * Constructor is used for internal usage only, do not create object from it. + */ + constructor( + ctx?: Context, + _id?: ModuleConfigClientID, + _dev?: boolean, + _directory?: string, + _generator?: string, + ) { + super(ctx) + + this._id = _id + this._dev = _dev + this._directory = _directory + this._generator = _generator + } + + /** + * A unique identifier for this ModuleConfigClient. + */ + id = async (): Promise => { + if (this._id) { + return this._id + } + + const ctx = this._ctx.select("id") + + const response: Awaited = await ctx.execute() + + return response + } + + /** + * If true, generate the client in developer mode. + */ + dev = async (): Promise => { + if (this._dev) { + return this._dev + } + + const ctx = this._ctx.select("dev") + + const response: Awaited = await ctx.execute() + + return response + } + + /** + * The directory the client is generated in. + */ + directory = async (): Promise => { + if (this._directory) { + return this._directory + } + + const ctx = this._ctx.select("directory") + + const response: Awaited = await ctx.execute() + + return response + } + + /** + * The generator to use + */ + generator = async (): Promise => { + if (this._generator) { + return this._generator + } + + const ctx = this._ctx.select("generator") + + const response: Awaited = await ctx.execute() + + return response + } +} + /** * The source needed to load and run a module, along with any metadata about the source such as versions/urls/etc. */ @@ -5472,6 +5565,23 @@ export class ModuleSource extends BaseClient { return response } + /** + * The clients generated for the module. + */ + configClients = async (): Promise => { + type configClients = { + id: ModuleConfigClientID + } + + const ctx = this._ctx.select("configClients").select("id") + + const response: Awaited = await ctx.execute() + + return response.map((r) => + new Client(ctx.copy()).loadModuleConfigClientFromID(r.id), + ) + } + /** * Whether an existing dagger.json for the module was found. */ @@ -5551,25 +5661,6 @@ export class ModuleSource extends BaseClient { return response } - /** - * Generates a client for the module. - * @param generator The generator to use - * @param outputDir The output directory for the generated client. - * @param opts.localSdk Use local SDK dependency - */ - generateClient = ( - generator: string, - outputDir: string, - opts?: ModuleSourceGenerateClientOpts, - ): Directory => { - const ctx = this._ctx.select("generateClient", { - generator, - outputDir, - ...opts, - }) - return new Directory(ctx) - } - /** * The generated files and directories made on top of the module source's context directory. */ @@ -5777,6 +5868,25 @@ export class ModuleSource extends BaseClient { return response } + /** + * Update the module source with a new client to generate. + * @param generator The generator to use + * @param outputDir The output directory for the generated client. + * @param opts.dev Generate in developer mode + */ + withClient = ( + generator: string, + outputDir: string, + opts?: ModuleSourceWithClientOpts, + ): ModuleSource => { + const ctx = this._ctx.select("withClient", { + generator, + outputDir, + ...opts, + }) + return new ModuleSource(ctx) + } + /** * Append the provided dependencies to the module source's dependency list. * @param dependencies The dependencies to append. @@ -6488,6 +6598,16 @@ export class Client extends BaseClient { return new ListTypeDef(ctx) } + /** + * Load a ModuleConfigClient from its ID. + */ + loadModuleConfigClientFromID = ( + id: ModuleConfigClientID, + ): ModuleConfigClient => { + const ctx = this._ctx.select("loadModuleConfigClientFromID", { id }) + return new ModuleConfigClient(ctx) + } + /** * Load a Module from its ID. */