From aaf55e4084f0bfd5bfc01db1ec36e81c6cb79854 Mon Sep 17 00:00:00 2001 From: Tom Chauveau Date: Wed, 22 May 2024 12:23:46 +0200 Subject: [PATCH 1/5] feat: support`AllowWildCard` and `AllowNotFound` Fixes #3338 Signed-off-by: Tom Chauveau --- core/container.go | 2 +- core/directory.go | 9 +++++++-- core/schema/directory.go | 16 ++++++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/container.go b/core/container.go index 9aa52609e4..60818f7ad2 100644 --- a/core/container.go +++ b/core/container.go @@ -506,7 +506,7 @@ func (container *Container) WithoutPath(ctx context.Context, destPath string) (* container = container.Clone() return container.writeToPath(ctx, path.Dir(destPath), func(dir *Directory) (*Directory, error) { - return dir.Without(ctx, path.Base(destPath)) + return dir.Without(ctx, path.Base(destPath), true, true) }) } diff --git a/core/directory.go b/core/directory.go index 6041042283..6c0882c6e4 100644 --- a/core/directory.go +++ b/core/directory.go @@ -677,7 +677,7 @@ func (dir *Directory) Diff(ctx context.Context, other *Directory) (*Directory, e return dir, nil } -func (dir *Directory) Without(ctx context.Context, path string) (*Directory, error) { +func (dir *Directory) Without(ctx context.Context, path string, allowWildCard bool, allowNotFound bool) (*Directory, error) { dir = dir.Clone() st, err := dir.State() @@ -685,8 +685,13 @@ func (dir *Directory) Without(ctx context.Context, path string) (*Directory, err return nil, err } + fmt.Println("path", path) + fmt.Println("allowNotfound", allowNotFound) + fmt.Println("allowWildCard", allowWildCard) + + path = filepath.Join(dir.Dir, path) - err = dir.SetState(ctx, st.File(llb.Rm(path, llb.WithAllowWildcard(true), llb.WithAllowNotFound(true)))) + err = dir.SetState(ctx, st.File(llb.Rm(path, llb.WithAllowWildcard(allowWildCard), llb.WithAllowNotFound(allowNotFound)))) if err != nil { return nil, err } diff --git a/core/schema/directory.go b/core/schema/directory.go index 62a7ae7359..2c38ee9863 100644 --- a/core/schema/directory.go +++ b/core/schema/directory.go @@ -55,7 +55,9 @@ func (s *directorySchema) Install() { ArgDoc("permissions", `Permission given to the copied file (e.g., 0600).`), dagql.Func("withoutFile", s.withoutFile). Doc(`Retrieves this directory with the file at the given path removed.`). - ArgDoc("path", `Location of the file to remove (e.g., "/file.txt").`), + ArgDoc("path", `Location of the file to remove (e.g., "/file.txt").`). + ArgDoc("allowWildCard", `Allow wildcards in the path (e.g., "*.txt").`). + ArgDoc("allowNotFound", `Allow the operation to not fail if the directory does not exist.`), dagql.Func("directory", s.subdirectory). Doc(`Retrieves a directory at the given path.`). ArgDoc("path", `Location of the directory to retrieve (e.g., "/src").`), @@ -71,7 +73,9 @@ func (s *directorySchema) Install() { ArgDoc("permissions", `Permission granted to the created directory (e.g., 0777).`), dagql.Func("withoutDirectory", s.withoutDirectory). Doc(`Retrieves this directory with the directory at the given path removed.`). - ArgDoc("path", `Location of the directory to remove (e.g., ".github/").`), + ArgDoc("path", `Location of the directory to remove (e.g., ".github/").`). + ArgDoc("allowWildCard", `Allow wildcards in the path (e.g., "*.txt").`). + ArgDoc("allowNotFound", `Allow the operation to not fail if the directory does not exist.`), dagql.Func("diff", s.diff). Doc(`Gets the difference between this directory and an another directory.`). ArgDoc("other", `Identifier of the directory to compare.`), @@ -233,18 +237,22 @@ func (s *directorySchema) withFiles(ctx context.Context, parent *core.Directory, type withoutDirectoryArgs struct { Path string + AllowWildCard bool `default:"true"` + AllowNotFound bool `default:"true"` } func (s *directorySchema) withoutDirectory(ctx context.Context, parent *core.Directory, args withoutDirectoryArgs) (*core.Directory, error) { - return parent.Without(ctx, args.Path) + return parent.Without(ctx, args.Path, args.AllowWildCard, args.AllowNotFound) } type withoutFileArgs struct { Path string + AllowWildCard bool `default:"true"` + AllowNotFound bool `default:"true"` } func (s *directorySchema) withoutFile(ctx context.Context, parent *core.Directory, args withoutFileArgs) (*core.Directory, error) { - return parent.Without(ctx, args.Path) + return parent.Without(ctx, args.Path, args.AllowWildCard, args.AllowNotFound) } type diffArgs struct { From 49a111710a8daef5a8344ea1163791755140e722 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 22 May 2024 11:24:15 +0100 Subject: [PATCH 2/5] ci: add sdk all group When we removed mage, we removed the easy helpers that allowed generating/linting/etc on all SDKs. That meant that making API changes became much more fiddly. This patch restores the "all" group, but this time directly in the dagger module. One day, we might remove this with a script, but we don't have that right now. Signed-off-by: Justin Chadwell --- ci/sdk.go | 30 +++++++++++++- ci/sdk_all.go | 95 ++++++++++++++++++++++++++++++++++++++++++++ ci/sdk_go.go | 2 +- ci/sdk_php.go | 2 +- ci/sdk_python.go | 2 +- ci/sdk_typescript.go | 2 +- 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 ci/sdk_all.go diff --git a/ci/sdk.go b/ci/sdk.go index e9b0d892ed..39eb6ab1dd 100644 --- a/ci/sdk.go +++ b/ci/sdk.go @@ -20,10 +20,36 @@ type SDK struct { Elixir *ElixirSDK // Develop the Dagger Rust SDK (experimental) Rust *RustSDK - // Develop the Dagger Java SDK (experimental) - Java *JavaSDK // Develop the Dagger PHP SDK (experimental) PHP *PHPSDK + // Develop the Dagger Java SDK (experimental) + Java *JavaSDK +} + +func (sdk *SDK) All() *AllSDK { + return &AllSDK{ + SDK: sdk, + } +} + +type sdkBase interface { + Lint(ctx context.Context) error + Test(ctx context.Context) error + Generate(ctx context.Context) (*Directory, error) + Bump(ctx context.Context, version string) (*Directory, error) +} + +func (sdk *SDK) allSDKs() []sdkBase { + return []sdkBase{ + sdk.Go, + sdk.Python, + sdk.Typescript, + sdk.Elixir, + sdk.Rust, + sdk.PHP, + // java isn't properly integrated to our release process yet + // sdk.Java, + } } func (ci *Dagger) installer(ctx context.Context, name string) (func(*Container) *Container, error) { diff --git a/ci/sdk_all.go b/ci/sdk_all.go new file mode 100644 index 0000000000..ac738f1c97 --- /dev/null +++ b/ci/sdk_all.go @@ -0,0 +1,95 @@ +package main + +import ( + "context" + + "golang.org/x/sync/errgroup" +) + +type AllSDK struct { + SDK *SDK // +private +} + +var _ sdkBase = AllSDK{} + +func (t AllSDK) Lint(ctx context.Context) error { + eg, ctx := errgroup.WithContext(ctx) + for _, sdk := range t.SDK.allSDKs() { + sdk := sdk + eg.Go(func() error { + return sdk.Lint(ctx) + }) + } + return eg.Wait() +} + +func (t AllSDK) Test(ctx context.Context) error { + eg, ctx := errgroup.WithContext(ctx) + for _, sdk := range t.SDK.allSDKs() { + sdk := sdk + eg.Go(func() error { + return sdk.Test(ctx) + }) + } + return eg.Wait() +} + +func (t AllSDK) Generate(ctx context.Context) (*Directory, error) { + eg, ctx := errgroup.WithContext(ctx) + dirs := make([]*Directory, len(t.SDK.allSDKs())) + for i, sdk := range t.SDK.allSDKs() { + i, sdk := i, sdk + eg.Go(func() error { + dir, err := sdk.Generate(ctx) + if err != nil { + return err + } + dir, err = dir.Sync(ctx) + if err != nil { + return err + } + dirs[i] = dir + return nil + }) + } + err := eg.Wait() + if err != nil { + return nil, err + } + + dir := dag.Directory() + for _, dir2 := range dirs { + dir = dir.WithDirectory("", dir2) + } + return dir, nil +} + +func (t AllSDK) Bump(ctx context.Context, version string) (*Directory, error) { + eg, ctx := errgroup.WithContext(ctx) + dirs := make([]*Directory, len(t.SDK.allSDKs())) + for i, sdk := range t.SDK.allSDKs() { + i, sdk := i, sdk + eg.Go(func() error { + dir, err := sdk.Bump(ctx, version) + if err != nil { + return err + } + dir, err = dir.Sync(ctx) + if err != nil { + return err + } + dirs[i] = dir + return nil + }) + } + err := eg.Wait() + if err != nil { + return nil, err + } + + dir := dag.Directory() + for _, dir2 := range dirs { + dir = dir.WithDirectory("", dir2) + } + return dir, nil +} diff --git a/ci/sdk_go.go b/ci/sdk_go.go index de230feae7..94ca12f5b5 100644 --- a/ci/sdk_go.go +++ b/ci/sdk_go.go @@ -100,7 +100,7 @@ func (t GoSDK) Publish( } // Bump the Go SDK's Engine dependency -func (t GoSDK) Bump(version string) (*Directory, error) { +func (t GoSDK) Bump(ctx context.Context, version string) (*Directory, error) { // trim leading v from version version = strings.TrimPrefix(version, "v") diff --git a/ci/sdk_php.go b/ci/sdk_php.go index 139016c257..3cef2feb7a 100644 --- a/ci/sdk_php.go +++ b/ci/sdk_php.go @@ -83,7 +83,7 @@ func (t PHPSDK) Publish( } // Bump the PHP SDK's Engine dependency -func (t PHPSDK) Bump(version string) (*Directory, error) { +func (t PHPSDK) Bump(ctx context.Context, version string) (*Directory, error) { version = strings.TrimPrefix(version, "v") content := fmt.Sprintf(` Date: Wed, 22 May 2024 11:33:13 +0100 Subject: [PATCH 3/5] feat: allow sending arbitrary signals to running service This can be done by attaching an optional signal argument to the existing `stop` method (which will wait until the process terminates after sending `stop`), or using the new `signal` method (which returns immediately). Signed-off-by: Justin Chadwell --- core/integration/services_test.go | 27 +++- core/schema/query.go | 1 + core/schema/service.go | 34 ++++- core/service.go | 135 ++++++++++++++++++-- core/services.go | 73 ++++++++--- docs/docs-graphql/schema.graphqls | 48 +++++++ sdk/elixir/lib/dagger/gen/service.ex | 17 ++- sdk/elixir/lib/dagger/gen/signal_types.ex | 146 ++++++++++++++++++++++ sdk/go/dagger.gen.go | 91 ++++++++++++++ sdk/php/generated/Service.php | 15 ++- sdk/php/generated/SignalTypes.php | 51 ++++++++ sdk/python/src/dagger/client/gen.py | 108 +++++++++++++++- sdk/rust/crates/dagger-sdk/src/gen.rs | 89 +++++++++++++ sdk/typescript/api/client.gen.ts | 78 +++++++++++- 14 files changed, 872 insertions(+), 41 deletions(-) create mode 100644 sdk/elixir/lib/dagger/gen/signal_types.ex create mode 100644 sdk/php/generated/SignalTypes.php diff --git a/core/integration/services_test.go b/core/integration/services_test.go index b5116d00f2..66499c1c27 100644 --- a/core/integration/services_test.go +++ b/core/integration/services_test.go @@ -1277,6 +1277,18 @@ func TestServiceStartStopKill(t *testing.T) { require.NoError(t, err) require.Empty(t, out) + // attempt to SIGABRT the service + _, err = httpSrv.Signal(ctx, dagger.Sigabrt) + require.NoError(t, err) + + // ensures that the subprocess gets the above signals + require.Eventually(t, func() bool { + out, err = fetch() + require.NoError(t, err) + fmt.Println(out) + return strings.Contains(out, "Aborted\n") + }, time.Minute, time.Second) + eg := errgroup.Group{} eg.Go(func() error { // attempt to SIGTERM the service @@ -1285,16 +1297,23 @@ func TestServiceStartStopKill(t *testing.T) { _, err := httpSrv.Stop(ctx) return err }) + eg.Go(func() error { + // attempt to SIGINT the service (same as above) + _, err := httpSrv.Stop(ctx, dagger.ServiceStopOpts{ + Signal: dagger.Sigint, + }) + return err + }) - // ensures that the subprocess gets SIGTERM + // ensures that the subprocess gets the above signals require.Eventually(t, func() bool { out, err = fetch() require.NoError(t, err) - return out == "Terminated\n" + return strings.Contains(out, "Terminated\n") && strings.Contains(out, "Interrupt\n") }, time.Minute, time.Second) eg.Go(func() error { - // attempt to SIGKILL the serive (this will work) + // attempt to SIGKILL the service (this will work) _, err := httpSrv.Stop(ctx, dagger.ServiceStopOpts{Kill: true}) return err }) @@ -1832,7 +1851,7 @@ import time def print_signal(signum, frame): with open("./signals.txt", "a+") as f: print(signal.strsignal(signum), file=f) -for sig in [signal.SIGINT, signal.SIGTERM]: +for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGABRT]: signal.signal(sig, print_signal) with socketserver.TCPServer(("", 8000), http.server.SimpleHTTPRequestHandler) as httpd: diff --git a/core/schema/query.go b/core/schema/query.go index d669dca1df..b3afb7eb33 100644 --- a/core/schema/query.go +++ b/core/schema/query.go @@ -32,6 +32,7 @@ func (s *querySchema) Install() { core.CacheSharingModes.Install(s.srv) core.TypeDefKinds.Install(s.srv) core.ModuleSourceKindEnum.Install(s.srv) + core.SignalTypesEnum.Install(s.srv) dagql.MustInputSpec(PipelineLabel{}).Install(s.srv) dagql.MustInputSpec(core.PortForward{}).Install(s.srv) diff --git a/core/schema/service.go b/core/schema/service.go index 2ffd26080c..e1e355d1e9 100644 --- a/core/schema/service.go +++ b/core/schema/service.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "runtime/debug" + "syscall" "github.com/dagger/dagger/core" "github.com/dagger/dagger/dagql" @@ -51,10 +52,16 @@ func (s *serviceSchema) Install() { ArgDoc("ports", `List of frontend/backend port mappings to forward.`, `Frontend is the port accepting traffic on the host, backend is the service port.`), + dagql.NodeFunc("signal", s.signal). + Impure("Imperatively mutates runtime state."). + Doc(`Signal the service.`). + ArgDoc("signal", `The signal to send to the service.`), + dagql.NodeFunc("stop", s.stop). Impure("Imperatively mutates runtime state."). Doc(`Stop the service.`). - ArgDoc("kill", `Immediately kill the service without waiting for a graceful exit`), + ArgDoc("kill", `Immediately kill the service without waiting for a graceful exit`). + ArgDoc("signal", `The signal to send to the service.`), }.Install(s.srv) } @@ -102,12 +109,33 @@ func (s *serviceSchema) start(ctx context.Context, parent dagql.Instance[*core.S return dagql.NewID[*core.Service](parent.ID()), nil } +type serviceSignalArgs struct { + Signal core.SignalTypes +} + +func (s *serviceSchema) signal(ctx context.Context, parent dagql.Instance[*core.Service], args serviceSignalArgs) (core.ServiceID, error) { + signal := args.Signal.ToSignal() + if err := parent.Self.Signal(ctx, parent.ID(), signal); err != nil { + return core.ServiceID{}, err + } + return dagql.NewID[*core.Service](parent.ID()), nil +} + type serviceStopArgs struct { - Kill bool `default:"false"` + Kill bool `default:"false"` + Signal dagql.Optional[core.SignalTypes] } func (s *serviceSchema) stop(ctx context.Context, parent dagql.Instance[*core.Service], args serviceStopArgs) (core.ServiceID, error) { - if err := parent.Self.Stop(ctx, parent.ID(), args.Kill); err != nil { + signal := syscall.SIGTERM + if args.Kill { + signal = syscall.SIGKILL + } + if args.Signal.Valid { + signal = args.Signal.Value.ToSignal() + } + + if err := parent.Self.Stop(ctx, parent.ID(), signal); err != nil { return core.ServiceID{}, err } return dagql.NewID[*core.Service](parent.ID()), nil diff --git a/core/service.go b/core/service.go index 0ea51d1c71..8cc9e54ba3 100644 --- a/core/service.go +++ b/core/service.go @@ -176,8 +176,12 @@ func (svc *Service) StartAndTrack(ctx context.Context, id *call.ID) error { return err } -func (svc *Service) Stop(ctx context.Context, id *call.ID, kill bool) error { - return svc.Query.Services.Stop(ctx, id, kill) +func (svc *Service) Stop(ctx context.Context, id *call.ID, signal syscall.Signal) error { + return svc.Query.Services.Stop(ctx, id, signal) +} + +func (svc *Service) Signal(ctx context.Context, id *call.ID, signal syscall.Signal) error { + return svc.Query.Services.Signal(ctx, id, signal) } func (svc *Service) Start( @@ -415,12 +419,15 @@ func (svc *Service) startContainer( span.End() }() - stopSvc := func(ctx context.Context, force bool) error { - sig := syscall.SIGTERM - if force { - sig = syscall.SIGKILL + signalSvc := func(ctx context.Context, signal syscall.Signal) error { + if err := svcProc.Signal(ctx, signal); err != nil { + return fmt.Errorf("signal: %w", err) } - if err := svcProc.Signal(ctx, sig); err != nil { + return nil + } + + stopSvc := func(ctx context.Context, signal syscall.Signal) error { + if err := svcProc.Signal(ctx, signal); err != nil { return fmt.Errorf("signal: %w", err) } select { @@ -454,8 +461,9 @@ func (svc *Service) startContainer( Digest: dig, ServerID: clientMetadata.ServerID, }, - Stop: stopSvc, - Wait: waitSvc, + Stop: stopSvc, + Signal: signalSvc, + Wait: waitSvc, }, nil case <-exited: if exitErr != nil { @@ -542,7 +550,7 @@ func (svc *Service) startTunnel(ctx context.Context, id *call.ID) (running *Runn }, Host: dialHost, Ports: ports, - Stop: func(_ context.Context, _ bool) error { + Stop: func(_ context.Context, _ syscall.Signal) error { stop() svcs.Detach(svcCtx, upstream) var errs []error @@ -638,7 +646,7 @@ func (svc *Service) startReverseTunnel(ctx context.Context, id *call.ID) (runnin }, Host: fullHost, Ports: checkPorts, - Stop: func(context.Context, bool) error { + Stop: func(_ context.Context, _ syscall.Signal) error { netNS.Release(svcCtx) stop() span.End() @@ -711,3 +719,108 @@ func (bndp *ServiceBindings) Merge(other ServiceBindings) { *bndp = merged } + +type SignalTypes string + +var SignalTypesEnum = dagql.NewEnum[SignalTypes]() + +// derived from syscall/zerrors_linux_amd64.go +var ( + SIGABRT = SignalTypesEnum.Register("SIGABRT") + SIGALRM = SignalTypesEnum.Register("SIGALRM") + SIGBUS = SignalTypesEnum.Register("SIGBUS") + SIGCHLD = SignalTypesEnum.Register("SIGCHLD") + SIGCLD = SignalTypesEnum.Register("SIGCLD") + SIGCONT = SignalTypesEnum.Register("SIGCONT") + SIGFPE = SignalTypesEnum.Register("SIGFPE") + SIGHUP = SignalTypesEnum.Register("SIGHUP") + SIGILL = SignalTypesEnum.Register("SIGILL") + SIGINT = SignalTypesEnum.Register("SIGINT") + SIGIO = SignalTypesEnum.Register("SIGIO") + SIGIOT = SignalTypesEnum.Register("SIGIOT") + SIGKILL = SignalTypesEnum.Register("SIGKILL") + SIGPIPE = SignalTypesEnum.Register("SIGPIPE") + SIGPOLL = SignalTypesEnum.Register("SIGPOLL") + SIGPROF = SignalTypesEnum.Register("SIGPROF") + SIGPWR = SignalTypesEnum.Register("SIGPWR") + SIGQUIT = SignalTypesEnum.Register("SIGQUIT") + SIGSEGV = SignalTypesEnum.Register("SIGSEGV") + SIGSTKFLT = SignalTypesEnum.Register("SIGSTKFLT") + SIGSTOP = SignalTypesEnum.Register("SIGSTOP") + SIGSYS = SignalTypesEnum.Register("SIGSYS") + SIGTERM = SignalTypesEnum.Register("SIGTERM") + SIGTRAP = SignalTypesEnum.Register("SIGTRAP") + SIGTSTP = SignalTypesEnum.Register("SIGTSTP") + SIGTTIN = SignalTypesEnum.Register("SIGTTIN") + SIGTTOU = SignalTypesEnum.Register("SIGTTOU") + SIGUNUSED = SignalTypesEnum.Register("SIGUNUSED") + SIGURG = SignalTypesEnum.Register("SIGURG") + SIGUSR1 = SignalTypesEnum.Register("SIGUSR1") + SIGUSR2 = SignalTypesEnum.Register("SIGUSR2") + SIGVTALRM = SignalTypesEnum.Register("SIGVTALRM") + SIGWINCH = SignalTypesEnum.Register("SIGWINCH") + SIGXCPU = SignalTypesEnum.Register("SIGXCPU") + SIGXFSZ = SignalTypesEnum.Register("SIGXFSZ") +) + +// derived from syscall/zerrors_linux_amd64.go +var allSignals = map[SignalTypes]syscall.Signal{ + "SIGABRT": syscall.SIGABRT, + "SIGALRM": syscall.SIGALRM, + "SIGBUS": syscall.SIGBUS, + "SIGCHLD": syscall.SIGCHLD, + "SIGCLD": syscall.SIGCLD, + "SIGCONT": syscall.SIGCONT, + "SIGFPE": syscall.SIGFPE, + "SIGHUP": syscall.SIGHUP, + "SIGILL": syscall.SIGILL, + "SIGINT": syscall.SIGINT, + "SIGIO": syscall.SIGIO, + "SIGIOT": syscall.SIGIOT, + "SIGKILL": syscall.SIGKILL, + "SIGPIPE": syscall.SIGPIPE, + "SIGPOLL": syscall.SIGPOLL, + "SIGPROF": syscall.SIGPROF, + "SIGPWR": syscall.SIGPWR, + "SIGQUIT": syscall.SIGQUIT, + "SIGSEGV": syscall.SIGSEGV, + "SIGSTKFLT": syscall.SIGSTKFLT, + "SIGSTOP": syscall.SIGSTOP, + "SIGSYS": syscall.SIGSYS, + "SIGTERM": syscall.SIGTERM, + "SIGTRAP": syscall.SIGTRAP, + "SIGTSTP": syscall.SIGTSTP, + "SIGTTIN": syscall.SIGTTIN, + "SIGTTOU": syscall.SIGTTOU, + "SIGUNUSED": syscall.SIGUNUSED, + "SIGURG": syscall.SIGURG, + "SIGUSR1": syscall.SIGUSR1, + "SIGUSR2": syscall.SIGUSR2, + "SIGVTALRM": syscall.SIGVTALRM, + "SIGWINCH": syscall.SIGWINCH, + "SIGXCPU": syscall.SIGXCPU, + "SIGXFSZ": syscall.SIGXFSZ, +} + +func (signal SignalTypes) Type() *ast.Type { + return &ast.Type{ + NamedType: "SignalTypes", + NonNull: true, + } +} + +func (signal SignalTypes) TypeDescription() string { + return "Signaltypes to be sent to processes." +} + +func (signal SignalTypes) Decoder() dagql.InputDecoder { + return SignalTypesEnum +} + +func (signal SignalTypes) ToLiteral() call.Literal { + return SignalTypesEnum.Literal(signal) +} + +func (signal SignalTypes) ToSignal() syscall.Signal { + return allSignals[signal] +} diff --git a/core/services.go b/core/services.go index 12e9ee43b1..7f9e0d7278 100644 --- a/core/services.go +++ b/core/services.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "sync" + "syscall" "time" bkgw "github.com/moby/buildkit/frontend/gateway/client" @@ -61,9 +62,13 @@ type RunningService struct { // or 0 frontend ports set to the same as the backend port. Ports []Port - // Stop forcibly stops the service. It is normally called after all clients - // have detached, but may also be called manually by the user. - Stop func(ctx context.Context, force bool) error + // Signal sends a specified signal to the service. + Signal func(ctx context.Context, signal syscall.Signal) error + + // Stop sends a specified signal to the service, and then waits for it to + // stop. It is normally called after all clients have detached, but may + // also be called manually by the user. + Stop func(ctx context.Context, signal syscall.Signal) error // Block until the service has exited or the provided context is canceled. Wait func(ctx context.Context) error @@ -236,8 +241,26 @@ func (ss *Services) StartBindings(ctx context.Context, bindings ServiceBindings) return detach, running, nil } +func (ss *Services) Signal(ctx context.Context, id *call.ID, signal syscall.Signal) error { + return ss.withRunningService(ctx, id, func(ctx context.Context, running *RunningService) error { + if running == nil { + return nil + } + return ss.signal(ctx, running, signal) + }) +} + // Stop stops the given service. If the service is not running, it is a no-op. -func (ss *Services) Stop(ctx context.Context, id *call.ID, kill bool) error { +func (ss *Services) Stop(ctx context.Context, id *call.ID, signal syscall.Signal) error { + return ss.withRunningService(ctx, id, func(ctx context.Context, running *RunningService) error { + if running == nil { + return nil + } + return ss.stop(ctx, running, signal) + }) +} + +func (ss *Services) withRunningService(ctx context.Context, id *call.ID, f func(context.Context, *RunningService) error) error { clientMetadata, err := engine.ClientMetadataFromContext(ctx) if err != nil { return err @@ -256,10 +279,10 @@ func (ss *Services) Stop(ctx context.Context, id *call.ID, kill bool) error { switch { case isRunning: - // running; stop it - return ss.stop(ctx, running, kill) + // running + return f(ctx, running) case isStarting: - // starting; wait for the attempt to finish and then stop it + // starting; wait for the attempt to finish starting.Wait() ss.l.Lock() @@ -267,15 +290,15 @@ func (ss *Services) Stop(ctx context.Context, id *call.ID, kill bool) error { ss.l.Unlock() if isRunning { - // starting succeeded as normal; now stop it - return ss.stop(ctx, running, kill) + // starting succeeded as normal + return f(ctx, running) } - // starting didn't work; nothing to do - return nil + // starting didn't work; call with nil + return f(ctx, nil) default: - // not starting or running; nothing to do - return nil + // not starting or running; call with nil + return f(ctx, nil) } } @@ -298,7 +321,7 @@ func (ss *Services) StopClientServices(ctx context.Context, serverID string) err bklog.G(ctx).Debugf("shutting down service %s", svc.Host) // force kill the service, users should manually shutdown services if they're // concerned about graceful termination - if err := ss.stop(ctx, svc, true); err != nil { + if err := ss.stop(ctx, svc, syscall.SIGKILL); err != nil { return fmt.Errorf("stop %s: %w", svc.Host, err) } return nil @@ -341,8 +364,22 @@ func (ss *Services) Detach(ctx context.Context, svc *RunningService) { go ss.stopGraceful(ctx, running, TerminateGracePeriod) } -func (ss *Services) stop(ctx context.Context, running *RunningService, force bool) error { - err := running.Stop(ctx, force) +func (ss *Services) signal(ctx context.Context, running *RunningService, signal syscall.Signal) error { + if running.Signal == nil { + // tunnels can't be signalled + return nil + } + + err := running.Signal(ctx, signal) + if err != nil { + return fmt.Errorf("signal: %w", err) + } + + return nil +} + +func (ss *Services) stop(ctx context.Context, running *RunningService, signal syscall.Signal) error { + err := running.Stop(ctx, signal) if err != nil { return fmt.Errorf("stop: %w", err) } @@ -359,10 +396,10 @@ func (ss *Services) stopGraceful(ctx context.Context, running *RunningService, t // attempt to gentle stop within a timeout cause := errors.New("service did not terminate") ctx2, _ := context.WithTimeoutCause(ctx, timeout, cause) - err := running.Stop(ctx2, false) + err := running.Stop(ctx2, syscall.SIGTERM) if context.Cause(ctx2) == cause { // service didn't terminate within timeout, so force it to stop - err = running.Stop(ctx, true) + err = running.Stop(ctx, syscall.SIGKILL) } if err != nil { return err diff --git a/docs/docs-graphql/schema.graphqls b/docs/docs-graphql/schema.graphqls index 53a7dad6fc..3bee5cd5f9 100644 --- a/docs/docs-graphql/schema.graphqls +++ b/docs/docs-graphql/schema.graphqls @@ -2464,6 +2464,12 @@ type Service { """Retrieves the list of ports provided by the service.""" ports: [Port!]! + """Signal the service.""" + signal( + """The signal to send to the service.""" + signal: SignalTypes! + ): ServiceID! + """ Start the service and wait for its health checks to succeed. @@ -2475,6 +2481,9 @@ type Service { stop( """Immediately kill the service without waiting for a graceful exit""" kill: Boolean = false + + """The signal to send to the service.""" + signal: SignalTypes ): ServiceID! """ @@ -2498,6 +2507,45 @@ The `ServiceID` scalar type represents an identifier for an object of type Servi """ scalar ServiceID +"""Signaltypes to be sent to processes.""" +enum SignalTypes { + SIGABRT + SIGALRM + SIGBUS + SIGCHLD + SIGCLD + SIGCONT + SIGFPE + SIGHUP + SIGILL + SIGINT + SIGIO + SIGIOT + SIGKILL + SIGPIPE + SIGPOLL + SIGPROF + SIGPWR + SIGQUIT + SIGSEGV + SIGSTKFLT + SIGSTOP + SIGSYS + SIGTERM + SIGTRAP + SIGTSTP + SIGTTIN + SIGTTOU + SIGUNUSED + SIGURG + SIGUSR1 + SIGUSR2 + SIGVTALRM + SIGWINCH + SIGXCPU + SIGXFSZ +} + """A Unix or TCP/IP socket that can be mounted into a container.""" type Socket { """A unique identifier for this Socket.""" diff --git a/sdk/elixir/lib/dagger/gen/service.ex b/sdk/elixir/lib/dagger/gen/service.ex index b51a700c97..ebc5afced4 100644 --- a/sdk/elixir/lib/dagger/gen/service.ex +++ b/sdk/elixir/lib/dagger/gen/service.ex @@ -67,6 +67,15 @@ defmodule Dagger.Service do end end + @doc "Signal the service." + @spec signal(t(), Dagger.SignalTypes.t()) :: {:ok, Dagger.ServiceID.t()} | {:error, term()} + def signal(%__MODULE__{} = service, signal) do + selection = + service.selection |> select("signal") |> put_arg("signal", signal) + + execute(selection, service.client) + end + @doc """ Start the service and wait for its health checks to succeed. @@ -81,10 +90,14 @@ defmodule Dagger.Service do end @doc "Stop the service." - @spec stop(t(), [{:kill, boolean() | nil}]) :: {:ok, Dagger.ServiceID.t()} | {:error, term()} + @spec stop(t(), [{:kill, boolean() | nil}, {:signal, Dagger.SignalTypes.t() | nil}]) :: + {:ok, Dagger.ServiceID.t()} | {:error, term()} def stop(%__MODULE__{} = service, optional_args \\ []) do selection = - service.selection |> select("stop") |> maybe_put_arg("kill", optional_args[:kill]) + service.selection + |> select("stop") + |> maybe_put_arg("kill", optional_args[:kill]) + |> maybe_put_arg("signal", optional_args[:signal]) execute(selection, service.client) end diff --git a/sdk/elixir/lib/dagger/gen/signal_types.ex b/sdk/elixir/lib/dagger/gen/signal_types.ex new file mode 100644 index 0000000000..0504cbf7e3 --- /dev/null +++ b/sdk/elixir/lib/dagger/gen/signal_types.ex @@ -0,0 +1,146 @@ +# This file generated by `dagger_codegen`. Please DO NOT EDIT. +defmodule Dagger.SignalTypes do + @moduledoc "Signaltypes to be sent to processes." + + @type t() :: + :SIGABRT + | :SIGALRM + | :SIGBUS + | :SIGCHLD + | :SIGCLD + | :SIGCONT + | :SIGFPE + | :SIGHUP + | :SIGILL + | :SIGINT + | :SIGIO + | :SIGIOT + | :SIGKILL + | :SIGPIPE + | :SIGPOLL + | :SIGPROF + | :SIGPWR + | :SIGQUIT + | :SIGSEGV + | :SIGSTKFLT + | :SIGSTOP + | :SIGSYS + | :SIGTERM + | :SIGTRAP + | :SIGTSTP + | :SIGTTIN + | :SIGTTOU + | :SIGUNUSED + | :SIGURG + | :SIGUSR1 + | :SIGUSR2 + | :SIGVTALRM + | :SIGWINCH + | :SIGXCPU + | :SIGXFSZ + + @spec sigabrt() :: :SIGABRT + def sigabrt(), do: :SIGABRT + + @spec sigalrm() :: :SIGALRM + def sigalrm(), do: :SIGALRM + + @spec sigbus() :: :SIGBUS + def sigbus(), do: :SIGBUS + + @spec sigchld() :: :SIGCHLD + def sigchld(), do: :SIGCHLD + + @spec sigcld() :: :SIGCLD + def sigcld(), do: :SIGCLD + + @spec sigcont() :: :SIGCONT + def sigcont(), do: :SIGCONT + + @spec sigfpe() :: :SIGFPE + def sigfpe(), do: :SIGFPE + + @spec sighup() :: :SIGHUP + def sighup(), do: :SIGHUP + + @spec sigill() :: :SIGILL + def sigill(), do: :SIGILL + + @spec sigint() :: :SIGINT + def sigint(), do: :SIGINT + + @spec sigio() :: :SIGIO + def sigio(), do: :SIGIO + + @spec sigiot() :: :SIGIOT + def sigiot(), do: :SIGIOT + + @spec sigkill() :: :SIGKILL + def sigkill(), do: :SIGKILL + + @spec sigpipe() :: :SIGPIPE + def sigpipe(), do: :SIGPIPE + + @spec sigpoll() :: :SIGPOLL + def sigpoll(), do: :SIGPOLL + + @spec sigprof() :: :SIGPROF + def sigprof(), do: :SIGPROF + + @spec sigpwr() :: :SIGPWR + def sigpwr(), do: :SIGPWR + + @spec sigquit() :: :SIGQUIT + def sigquit(), do: :SIGQUIT + + @spec sigsegv() :: :SIGSEGV + def sigsegv(), do: :SIGSEGV + + @spec sigstkflt() :: :SIGSTKFLT + def sigstkflt(), do: :SIGSTKFLT + + @spec sigstop() :: :SIGSTOP + def sigstop(), do: :SIGSTOP + + @spec sigsys() :: :SIGSYS + def sigsys(), do: :SIGSYS + + @spec sigterm() :: :SIGTERM + def sigterm(), do: :SIGTERM + + @spec sigtrap() :: :SIGTRAP + def sigtrap(), do: :SIGTRAP + + @spec sigtstp() :: :SIGTSTP + def sigtstp(), do: :SIGTSTP + + @spec sigttin() :: :SIGTTIN + def sigttin(), do: :SIGTTIN + + @spec sigttou() :: :SIGTTOU + def sigttou(), do: :SIGTTOU + + @spec sigunused() :: :SIGUNUSED + def sigunused(), do: :SIGUNUSED + + @spec sigurg() :: :SIGURG + def sigurg(), do: :SIGURG + + @spec sigusr1() :: :SIGUSR1 + def sigusr1(), do: :SIGUSR1 + + @spec sigusr2() :: :SIGUSR2 + def sigusr2(), do: :SIGUSR2 + + @spec sigvtalrm() :: :SIGVTALRM + def sigvtalrm(), do: :SIGVTALRM + + @spec sigwinch() :: :SIGWINCH + def sigwinch(), do: :SIGWINCH + + @spec sigxcpu() :: :SIGXCPU + def sigxcpu(), do: :SIGXCPU + + @spec sigxfsz() :: :SIGXFSZ + def sigxfsz(), do: :SIGXFSZ +end diff --git a/sdk/go/dagger.gen.go b/sdk/go/dagger.gen.go index 0e51e52f0f..c247722ec7 100644 --- a/sdk/go/dagger.gen.go +++ b/sdk/go/dagger.gen.go @@ -6277,6 +6277,7 @@ type Service struct { endpoint *string hostname *string id *ServiceID + signal *ServiceID start *ServiceID stop *ServiceID up *Void @@ -6409,6 +6410,14 @@ func (r *Service) Ports(ctx context.Context) ([]Port, error) { return convert(response), nil } +// Signal the service. +func (r *Service) Signal(ctx context.Context, signal SignalTypes) (*Service, error) { + q := r.query.Select("signal") + q = q.Arg("signal", signal) + + return r, q.Execute(ctx) +} + // Start the service and wait for its health checks to succeed. // // Services bound to a Container do not need to be manually started. @@ -6422,6 +6431,8 @@ func (r *Service) Start(ctx context.Context) (*Service, error) { type ServiceStopOpts struct { // Immediately kill the service without waiting for a graceful exit Kill bool + // The signal to send to the service. + Signal SignalTypes } // Stop the service. @@ -6432,6 +6443,10 @@ func (r *Service) Stop(ctx context.Context, opts ...ServiceStopOpts) (*Service, if !querybuilder.IsZeroValue(opts[i].Kill) { q = q.Arg("kill", opts[i].Kill) } + // `signal` optional argument + if !querybuilder.IsZeroValue(opts[i].Signal) { + q = q.Arg("signal", opts[i].Signal) + } } return r, q.Execute(ctx) @@ -6925,6 +6940,82 @@ const ( Udp NetworkProtocol = "UDP" ) +type SignalTypes string + +func (SignalTypes) IsEnum() {} + +const ( + Sigabrt SignalTypes = "SIGABRT" + + Sigalrm SignalTypes = "SIGALRM" + + Sigbus SignalTypes = "SIGBUS" + + Sigchld SignalTypes = "SIGCHLD" + + Sigcld SignalTypes = "SIGCLD" + + Sigcont SignalTypes = "SIGCONT" + + Sigfpe SignalTypes = "SIGFPE" + + Sighup SignalTypes = "SIGHUP" + + Sigill SignalTypes = "SIGILL" + + Sigint SignalTypes = "SIGINT" + + Sigio SignalTypes = "SIGIO" + + Sigiot SignalTypes = "SIGIOT" + + Sigkill SignalTypes = "SIGKILL" + + Sigpipe SignalTypes = "SIGPIPE" + + Sigpoll SignalTypes = "SIGPOLL" + + Sigprof SignalTypes = "SIGPROF" + + Sigpwr SignalTypes = "SIGPWR" + + Sigquit SignalTypes = "SIGQUIT" + + Sigsegv SignalTypes = "SIGSEGV" + + Sigstkflt SignalTypes = "SIGSTKFLT" + + Sigstop SignalTypes = "SIGSTOP" + + Sigsys SignalTypes = "SIGSYS" + + Sigterm SignalTypes = "SIGTERM" + + Sigtrap SignalTypes = "SIGTRAP" + + Sigtstp SignalTypes = "SIGTSTP" + + Sigttin SignalTypes = "SIGTTIN" + + Sigttou SignalTypes = "SIGTTOU" + + Sigunused SignalTypes = "SIGUNUSED" + + Sigurg SignalTypes = "SIGURG" + + Sigusr1 SignalTypes = "SIGUSR1" + + Sigusr2 SignalTypes = "SIGUSR2" + + Sigvtalrm SignalTypes = "SIGVTALRM" + + Sigwinch SignalTypes = "SIGWINCH" + + Sigxcpu SignalTypes = "SIGXCPU" + + Sigxfsz SignalTypes = "SIGXFSZ" +) + type TypeDefKind string func (TypeDefKind) IsEnum() {} diff --git a/sdk/php/generated/Service.php b/sdk/php/generated/Service.php index fccdf6600e..f618587b65 100644 --- a/sdk/php/generated/Service.php +++ b/sdk/php/generated/Service.php @@ -59,6 +59,16 @@ public function ports(): array return (array)$this->queryLeaf($leafQueryBuilder, 'ports'); } + /** + * Signal the service. + */ + public function signal(SignalTypes $signal): ServiceId + { + $leafQueryBuilder = new \Dagger\Client\QueryBuilder('signal'); + $leafQueryBuilder->setArgument('signal', $signal); + return new \Dagger\ServiceId((string)$this->queryLeaf($leafQueryBuilder, 'signal')); + } + /** * Start the service and wait for its health checks to succeed. * @@ -73,12 +83,15 @@ public function start(): ServiceId /** * Stop the service. */ - public function stop(?bool $kill = false): ServiceId + public function stop(?bool $kill = false, ?SignalTypes $signal = null): ServiceId { $leafQueryBuilder = new \Dagger\Client\QueryBuilder('stop'); if (null !== $kill) { $leafQueryBuilder->setArgument('kill', $kill); } + if (null !== $signal) { + $leafQueryBuilder->setArgument('signal', $signal); + } return new \Dagger\ServiceId((string)$this->queryLeaf($leafQueryBuilder, 'stop')); } diff --git a/sdk/php/generated/SignalTypes.php b/sdk/php/generated/SignalTypes.php new file mode 100644 index 0000000000..006b4e2a48 --- /dev/null +++ b/sdk/php/generated/SignalTypes.php @@ -0,0 +1,51 @@ + Self: + """Signal the service. + + Parameters + ---------- + signal: + The signal to send to the service. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("signal", signal), + ] + _ctx = self._select("signal", _args) + _id = await _ctx.execute(ServiceID) + _ctx = Client.from_context(_ctx)._select("loadServiceFromID", [Arg("id", _id)]) + return Service(_ctx) + async def start(self) -> Self: """Start the service and wait for its health checks to succeed. @@ -6431,13 +6528,20 @@ async def start(self) -> Self: _ctx = Client.from_context(_ctx)._select("loadServiceFromID", [Arg("id", _id)]) return Service(_ctx) - async def stop(self, *, kill: bool | None = False) -> Self: + async def stop( + self, + *, + kill: bool | None = False, + signal: SignalTypes | None = None, + ) -> Self: """Stop the service. Parameters ---------- kill: Immediately kill the service without waiting for a graceful exit + signal: + The signal to send to the service. Raises ------ @@ -6448,6 +6552,7 @@ async def stop(self, *, kill: bool | None = False) -> Self: """ _args = [ Arg("kill", kill, False), + Arg("signal", signal, None), ] _ctx = self._select("stop", _args) _id = await _ctx.execute(ServiceID) @@ -6882,6 +6987,7 @@ def with_(self, cb: Callable[["TypeDef"], "TypeDef"]) -> "TypeDef": "SecretID", "Service", "ServiceID", + "SignalTypes", "Socket", "SocketID", "Terminal", diff --git a/sdk/rust/crates/dagger-sdk/src/gen.rs b/sdk/rust/crates/dagger-sdk/src/gen.rs index 23ac836b9f..58dea20741 100644 --- a/sdk/rust/crates/dagger-sdk/src/gen.rs +++ b/sdk/rust/crates/dagger-sdk/src/gen.rs @@ -6690,6 +6690,9 @@ pub struct ServiceStopOpts { /// Immediately kill the service without waiting for a graceful exit #[builder(setter(into, strip_option), default)] pub kill: Option, + /// The signal to send to the service. + #[builder(setter(into, strip_option), default)] + pub signal: Option, } #[derive(Builder, Debug, PartialEq)] pub struct ServiceUpOpts { @@ -6752,6 +6755,16 @@ impl Service { graphql_client: self.graphql_client.clone(), }]; } + /// Signal the service. + /// + /// # Arguments + /// + /// * `signal` - The signal to send to the service. + pub async fn signal(&self, signal: SignalTypes) -> Result { + let mut query = self.selection.select("signal"); + query = query.arg("signal", signal); + query.execute(self.graphql_client.clone()).await + } /// Start the service and wait for its health checks to succeed. /// Services bound to a Container do not need to be manually started. pub async fn start(&self) -> Result { @@ -6777,6 +6790,9 @@ impl Service { if let Some(kill) = opts.kill { query = query.arg("kill", kill); } + if let Some(signal) = opts.signal { + query = query.arg("signal", signal); + } query.execute(self.graphql_client.clone()).await } /// Creates a tunnel that forwards traffic from the caller's network to this service. @@ -7195,6 +7211,79 @@ pub enum NetworkProtocol { Udp, } #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub enum SignalTypes { + #[serde(rename = "SIGABRT")] + Sigabrt, + #[serde(rename = "SIGALRM")] + Sigalrm, + #[serde(rename = "SIGBUS")] + Sigbus, + #[serde(rename = "SIGCHLD")] + Sigchld, + #[serde(rename = "SIGCLD")] + Sigcld, + #[serde(rename = "SIGCONT")] + Sigcont, + #[serde(rename = "SIGFPE")] + Sigfpe, + #[serde(rename = "SIGHUP")] + Sighup, + #[serde(rename = "SIGILL")] + Sigill, + #[serde(rename = "SIGINT")] + Sigint, + #[serde(rename = "SIGIO")] + Sigio, + #[serde(rename = "SIGIOT")] + Sigiot, + #[serde(rename = "SIGKILL")] + Sigkill, + #[serde(rename = "SIGPIPE")] + Sigpipe, + #[serde(rename = "SIGPOLL")] + Sigpoll, + #[serde(rename = "SIGPROF")] + Sigprof, + #[serde(rename = "SIGPWR")] + Sigpwr, + #[serde(rename = "SIGQUIT")] + Sigquit, + #[serde(rename = "SIGSEGV")] + Sigsegv, + #[serde(rename = "SIGSTKFLT")] + Sigstkflt, + #[serde(rename = "SIGSTOP")] + Sigstop, + #[serde(rename = "SIGSYS")] + Sigsys, + #[serde(rename = "SIGTERM")] + Sigterm, + #[serde(rename = "SIGTRAP")] + Sigtrap, + #[serde(rename = "SIGTSTP")] + Sigtstp, + #[serde(rename = "SIGTTIN")] + Sigttin, + #[serde(rename = "SIGTTOU")] + Sigttou, + #[serde(rename = "SIGUNUSED")] + Sigunused, + #[serde(rename = "SIGURG")] + Sigurg, + #[serde(rename = "SIGUSR1")] + Sigusr1, + #[serde(rename = "SIGUSR2")] + Sigusr2, + #[serde(rename = "SIGVTALRM")] + Sigvtalrm, + #[serde(rename = "SIGWINCH")] + Sigwinch, + #[serde(rename = "SIGXCPU")] + Sigxcpu, + #[serde(rename = "SIGXFSZ")] + Sigxfsz, +} +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub enum TypeDefKind { #[serde(rename = "BOOLEAN_KIND")] BooleanKind, diff --git a/sdk/typescript/api/client.gen.ts b/sdk/typescript/api/client.gen.ts index 37845dd5b4..2c6d8b32d2 100644 --- a/sdk/typescript/api/client.gen.ts +++ b/sdk/typescript/api/client.gen.ts @@ -969,6 +969,11 @@ export type ServiceStopOpts = { * Immediately kill the service without waiting for a graceful exit */ kill?: boolean + + /** + * The signal to send to the service. + */ + signal?: SignalTypes } export type ServiceUpOpts = { @@ -990,6 +995,46 @@ export type ServiceUpOpts = { */ export type ServiceID = string & { __ServiceID: never } +/** + * Signaltypes to be sent to processes. + */ +export enum SignalTypes { + Sigabrt = "SIGABRT", + Sigalrm = "SIGALRM", + Sigbus = "SIGBUS", + Sigchld = "SIGCHLD", + Sigcld = "SIGCLD", + Sigcont = "SIGCONT", + Sigfpe = "SIGFPE", + Sighup = "SIGHUP", + Sigill = "SIGILL", + Sigint = "SIGINT", + Sigio = "SIGIO", + Sigiot = "SIGIOT", + Sigkill = "SIGKILL", + Sigpipe = "SIGPIPE", + Sigpoll = "SIGPOLL", + Sigprof = "SIGPROF", + Sigpwr = "SIGPWR", + Sigquit = "SIGQUIT", + Sigsegv = "SIGSEGV", + Sigstkflt = "SIGSTKFLT", + Sigstop = "SIGSTOP", + Sigsys = "SIGSYS", + Sigterm = "SIGTERM", + Sigtrap = "SIGTRAP", + Sigtstp = "SIGTSTP", + Sigttin = "SIGTTIN", + Sigttou = "SIGTTOU", + Sigunused = "SIGUNUSED", + Sigurg = "SIGURG", + Sigusr1 = "SIGUSR1", + Sigusr2 = "SIGUSR2", + Sigvtalrm = "SIGVTALRM", + Sigwinch = "SIGWINCH", + Sigxcpu = "SIGXCPU", + Sigxfsz = "SIGXFSZ", +} /** * The `SocketID` scalar type represents an identifier for an object of type Socket. */ @@ -8176,6 +8221,7 @@ export class Service extends BaseClient { private readonly _id?: ServiceID = undefined private readonly _endpoint?: string = undefined private readonly _hostname?: string = undefined + private readonly _signal?: ServiceID = undefined private readonly _start?: ServiceID = undefined private readonly _stop?: ServiceID = undefined private readonly _up?: Void = undefined @@ -8188,6 +8234,7 @@ export class Service extends BaseClient { _id?: ServiceID, _endpoint?: string, _hostname?: string, + _signal?: ServiceID, _start?: ServiceID, _stop?: ServiceID, _up?: Void, @@ -8197,6 +8244,7 @@ export class Service extends BaseClient { this._id = _id this._endpoint = _endpoint this._hostname = _hostname + this._signal = _signal this._start = _start this._stop = _stop this._up = _up @@ -8310,6 +8358,29 @@ export class Service extends BaseClient { ) } + /** + * Signal the service. + * @param signal The signal to send to the service. + */ + signal = async (signal: SignalTypes): Promise => { + const metadata: Metadata = { + signal: { is_enum: true }, + } + + await computeQuery( + [ + ...this._queryTree, + { + operation: "signal", + args: { signal, __metadata: metadata }, + }, + ], + await this._ctx.connection(), + ) + + return this + } + /** * Start the service and wait for its health checks to succeed. * @@ -8332,14 +8403,19 @@ export class Service extends BaseClient { /** * Stop the service. * @param opts.kill Immediately kill the service without waiting for a graceful exit + * @param opts.signal The signal to send to the service. */ stop = async (opts?: ServiceStopOpts): Promise => { + const metadata: Metadata = { + signal: { is_enum: true }, + } + await computeQuery( [ ...this._queryTree, { operation: "stop", - args: { ...opts }, + args: { ...opts, __metadata: metadata }, }, ], await this._ctx.connection(), From 5adba3b60dcc48cd61fa615fbe38ab73ed9601d5 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 22 May 2024 11:25:51 +0100 Subject: [PATCH 4/5] sdk(typescript): handle required enum options Without this, the metadata would not be passed for a required enum arg. Signed-off-by: Justin Chadwell --- .../generator/typescript/templates/src/method_solve.ts.gtpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/codegen/generator/typescript/templates/src/method_solve.ts.gtpl b/cmd/codegen/generator/typescript/templates/src/method_solve.ts.gtpl index 2e679d2d7a..1ad012210e 100644 --- a/cmd/codegen/generator/typescript/templates/src/method_solve.ts.gtpl +++ b/cmd/codegen/generator/typescript/templates/src/method_solve.ts.gtpl @@ -77,8 +77,9 @@ {{- with $optionals }} {{- if $required }}, {{ end }} - {{- "" }}...opts{{- if gt (len $enums) 0 -}}, __metadata: metadata{{- end -}} + {{- "" }}...opts {{- end }} + {{- if gt (len $enums) 0 -}}, __metadata: metadata{{- end -}} {{- "" }} }, {{- end }} }, From 7c80e4d572f081e8da86b1e1b469c96ef489143b Mon Sep 17 00:00:00 2001 From: Tom Chauveau Date: Wed, 22 May 2024 13:33:20 +0200 Subject: [PATCH 5/5] chore: regenerate clients Signed-off-by: Tom Chauveau --- core/integration/directory_test.go | 13 +++++ go.mod | 3 +- go.sum | 5 -- sdk/elixir/lib/dagger/gen/directory.ex | 26 +++++++--- sdk/go/dagger.gen.go | 40 ++++++++++++++- sdk/php/generated/Directory.php | 20 +++++++- sdk/python/src/dagger/client/gen.py | 28 ++++++++++- sdk/rust/crates/dagger-sdk/src/gen.rs | 70 ++++++++++++++++++++++++++ sdk/typescript/api/client.gen.ts | 39 ++++++++++++-- 9 files changed, 221 insertions(+), 23 deletions(-) diff --git a/core/integration/directory_test.go b/core/integration/directory_test.go index 97dcbc3542..3643c67f23 100644 --- a/core/integration/directory_test.go +++ b/core/integration/directory_test.go @@ -521,6 +521,13 @@ func TestDirectoryWithoutDirectoryWithoutFile(t *testing.T) { require.NoError(t, err) require.Equal(t, []string{"some-dir", "some-file"}, entries) + // Test without dir with allow not found = false + _, err = dir1. + WithoutDirectory("non-existant", dagger.DirectoryWithoutDirectoryOpts{AllowNotFound: false}). + Entries(ctx) + + require.Error(t, err) + dir := c.Directory(). WithNewFile("foo.txt", "foo"). WithNewFile("a/bar.txt", "bar"). @@ -550,6 +557,12 @@ func TestDirectoryWithoutDirectoryWithoutFile(t *testing.T) { require.NoError(t, err) require.Equal(t, []string{"data.json"}, entries) + _, err = dir. + WithoutDirectory("b/*.txt", dagger.DirectoryWithoutDirectoryOpts{AllowWildCard: false}). + Entries(ctx) + + require.Error(t, err) + entries, err = dir. WithoutFile("c/*a1*"). Entries(ctx, dagger.DirectoryEntriesOpts{Path: "c"}) diff --git a/go.mod b/go.mod index 8e04adf7cd..6b90627860 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/opencontainers/runc v1.1.12 github.com/opencontainers/runtime-spec v1.2.0 github.com/pelletier/go-toml v1.9.5 - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/procfs v0.12.0 github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e @@ -263,7 +263,6 @@ require ( go.opentelemetry.io/otel/metric v1.26.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 2d3e25a159..a0c0b92217 100644 --- a/go.sum +++ b/go.sum @@ -824,8 +824,6 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -914,7 +912,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -999,8 +996,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= diff --git a/sdk/elixir/lib/dagger/gen/directory.ex b/sdk/elixir/lib/dagger/gen/directory.ex index c2c140008e..f91f978299 100644 --- a/sdk/elixir/lib/dagger/gen/directory.ex +++ b/sdk/elixir/lib/dagger/gen/directory.ex @@ -257,10 +257,17 @@ defmodule Dagger.Directory do end @doc "Retrieves this directory with the directory at the given path removed." - @spec without_directory(t(), String.t()) :: Dagger.Directory.t() - def without_directory(%__MODULE__{} = directory, path) do + @spec without_directory(t(), String.t(), [ + {:allow_wild_card, boolean() | nil}, + {:allow_not_found, boolean() | nil} + ]) :: Dagger.Directory.t() + def without_directory(%__MODULE__{} = directory, path, optional_args \\ []) do selection = - directory.selection |> select("withoutDirectory") |> put_arg("path", path) + directory.selection + |> select("withoutDirectory") + |> put_arg("path", path) + |> maybe_put_arg("allowWildCard", optional_args[:allow_wild_card]) + |> maybe_put_arg("allowNotFound", optional_args[:allow_not_found]) %Dagger.Directory{ selection: selection, @@ -269,10 +276,17 @@ defmodule Dagger.Directory do end @doc "Retrieves this directory with the file at the given path removed." - @spec without_file(t(), String.t()) :: Dagger.Directory.t() - def without_file(%__MODULE__{} = directory, path) do + @spec without_file(t(), String.t(), [ + {:allow_wild_card, boolean() | nil}, + {:allow_not_found, boolean() | nil} + ]) :: Dagger.Directory.t() + def without_file(%__MODULE__{} = directory, path, optional_args \\ []) do selection = - directory.selection |> select("withoutFile") |> put_arg("path", path) + directory.selection + |> select("withoutFile") + |> put_arg("path", path) + |> maybe_put_arg("allowWildCard", optional_args[:allow_wild_card]) + |> maybe_put_arg("allowNotFound", optional_args[:allow_not_found]) %Dagger.Directory{ selection: selection, diff --git a/sdk/go/dagger.gen.go b/sdk/go/dagger.gen.go index c247722ec7..5cccf0108c 100644 --- a/sdk/go/dagger.gen.go +++ b/sdk/go/dagger.gen.go @@ -2234,9 +2234,27 @@ func (r *Directory) WithTimestamps(timestamp int) *Directory { } } +// DirectoryWithoutDirectoryOpts contains options for Directory.WithoutDirectory +type DirectoryWithoutDirectoryOpts struct { + // Allow wildcards in the path (e.g., "*.txt"). + AllowWildCard bool + // Allow the operation to not fail if the directory does not exist. + AllowNotFound bool +} + // Retrieves this directory with the directory at the given path removed. -func (r *Directory) WithoutDirectory(path string) *Directory { +func (r *Directory) WithoutDirectory(path string, opts ...DirectoryWithoutDirectoryOpts) *Directory { q := r.query.Select("withoutDirectory") + for i := len(opts) - 1; i >= 0; i-- { + // `allowWildCard` optional argument + if !querybuilder.IsZeroValue(opts[i].AllowWildCard) { + q = q.Arg("allowWildCard", opts[i].AllowWildCard) + } + // `allowNotFound` optional argument + if !querybuilder.IsZeroValue(opts[i].AllowNotFound) { + q = q.Arg("allowNotFound", opts[i].AllowNotFound) + } + } q = q.Arg("path", path) return &Directory{ @@ -2244,9 +2262,27 @@ func (r *Directory) WithoutDirectory(path string) *Directory { } } +// DirectoryWithoutFileOpts contains options for Directory.WithoutFile +type DirectoryWithoutFileOpts struct { + // Allow wildcards in the path (e.g., "*.txt"). + AllowWildCard bool + // Allow the operation to not fail if the directory does not exist. + AllowNotFound bool +} + // Retrieves this directory with the file at the given path removed. -func (r *Directory) WithoutFile(path string) *Directory { +func (r *Directory) WithoutFile(path string, opts ...DirectoryWithoutFileOpts) *Directory { q := r.query.Select("withoutFile") + for i := len(opts) - 1; i >= 0; i-- { + // `allowWildCard` optional argument + if !querybuilder.IsZeroValue(opts[i].AllowWildCard) { + q = q.Arg("allowWildCard", opts[i].AllowWildCard) + } + // `allowNotFound` optional argument + if !querybuilder.IsZeroValue(opts[i].AllowNotFound) { + q = q.Arg("allowNotFound", opts[i].AllowNotFound) + } + } q = q.Arg("path", path) return &Directory{ diff --git a/sdk/php/generated/Directory.php b/sdk/php/generated/Directory.php index cd06a9255c..559530e6b5 100644 --- a/sdk/php/generated/Directory.php +++ b/sdk/php/generated/Directory.php @@ -244,20 +244,36 @@ public function withTimestamps(int $timestamp): Directory /** * Retrieves this directory with the directory at the given path removed. */ - public function withoutDirectory(string $path): Directory + public function withoutDirectory( + string $path, + ?bool $allowWildCard = true, + ?bool $allowNotFound = true, + ): Directory { $innerQueryBuilder = new \Dagger\Client\QueryBuilder('withoutDirectory'); $innerQueryBuilder->setArgument('path', $path); + if (null !== $allowWildCard) { + $innerQueryBuilder->setArgument('allowWildCard', $allowWildCard); + } + if (null !== $allowNotFound) { + $innerQueryBuilder->setArgument('allowNotFound', $allowNotFound); + } return new \Dagger\Directory($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); } /** * Retrieves this directory with the file at the given path removed. */ - public function withoutFile(string $path): Directory + public function withoutFile(string $path, ?bool $allowWildCard = true, ?bool $allowNotFound = true): Directory { $innerQueryBuilder = new \Dagger\Client\QueryBuilder('withoutFile'); $innerQueryBuilder->setArgument('path', $path); + if (null !== $allowWildCard) { + $innerQueryBuilder->setArgument('allowWildCard', $allowWildCard); + } + if (null !== $allowNotFound) { + $innerQueryBuilder->setArgument('allowNotFound', $allowNotFound); + } return new \Dagger\Directory($this->client, $this->queryBuilderChain->chain($innerQueryBuilder)); } } diff --git a/sdk/python/src/dagger/client/gen.py b/sdk/python/src/dagger/client/gen.py index d7ba7ccde7..632374fcda 100644 --- a/sdk/python/src/dagger/client/gen.py +++ b/sdk/python/src/dagger/client/gen.py @@ -2568,30 +2568,54 @@ def with_timestamps(self, timestamp: int) -> Self: _ctx = self._select("withTimestamps", _args) return Directory(_ctx) - def without_directory(self, path: str) -> Self: + def without_directory( + self, + path: str, + *, + allow_wild_card: bool | None = True, + allow_not_found: bool | None = True, + ) -> Self: """Retrieves this directory with the directory at the given path removed. Parameters ---------- path: Location of the directory to remove (e.g., ".github/"). + allow_wild_card: + Allow wildcards in the path (e.g., "*.txt"). + allow_not_found: + Allow the operation to not fail if the directory does not exist. """ _args = [ Arg("path", path), + Arg("allowWildCard", allow_wild_card, True), + Arg("allowNotFound", allow_not_found, True), ] _ctx = self._select("withoutDirectory", _args) return Directory(_ctx) - def without_file(self, path: str) -> Self: + def without_file( + self, + path: str, + *, + allow_wild_card: bool | None = True, + allow_not_found: bool | None = True, + ) -> Self: """Retrieves this directory with the file at the given path removed. Parameters ---------- path: Location of the file to remove (e.g., "/file.txt"). + allow_wild_card: + Allow wildcards in the path (e.g., "*.txt"). + allow_not_found: + Allow the operation to not fail if the directory does not exist. """ _args = [ Arg("path", path), + Arg("allowWildCard", allow_wild_card, True), + Arg("allowNotFound", allow_not_found, True), ] _ctx = self._select("withoutFile", _args) return Directory(_ctx) diff --git a/sdk/rust/crates/dagger-sdk/src/gen.rs b/sdk/rust/crates/dagger-sdk/src/gen.rs index 58dea20741..fce02e9172 100644 --- a/sdk/rust/crates/dagger-sdk/src/gen.rs +++ b/sdk/rust/crates/dagger-sdk/src/gen.rs @@ -3341,6 +3341,24 @@ pub struct DirectoryWithNewFileOpts { #[builder(setter(into, strip_option), default)] pub permissions: Option, } +#[derive(Builder, Debug, PartialEq)] +pub struct DirectoryWithoutDirectoryOpts { + /// Allow the operation to not fail if the directory does not exist. + #[builder(setter(into, strip_option), default)] + pub allow_not_found: Option, + /// Allow wildcards in the path (e.g., "*.txt"). + #[builder(setter(into, strip_option), default)] + pub allow_wild_card: Option, +} +#[derive(Builder, Debug, PartialEq)] +pub struct DirectoryWithoutFileOpts { + /// Allow the operation to not fail if the directory does not exist. + #[builder(setter(into, strip_option), default)] + pub allow_not_found: Option, + /// Allow wildcards in the path (e.g., "*.txt"). + #[builder(setter(into, strip_option), default)] + pub allow_wild_card: Option, +} impl Directory { /// Load the directory as a Dagger module /// @@ -3830,6 +3848,7 @@ impl Directory { /// # Arguments /// /// * `path` - Location of the directory to remove (e.g., ".github/"). + /// * `opt` - optional argument, see inner type for documentation, use _opts to use pub fn without_directory(&self, path: impl Into) -> Directory { let mut query = self.selection.select("withoutDirectory"); query = query.arg("path", path.into()); @@ -3839,11 +3858,37 @@ impl Directory { graphql_client: self.graphql_client.clone(), } } + /// Retrieves this directory with the directory at the given path removed. + /// + /// # Arguments + /// + /// * `path` - Location of the directory to remove (e.g., ".github/"). + /// * `opt` - optional argument, see inner type for documentation, use _opts to use + pub fn without_directory_opts( + &self, + path: impl Into, + opts: DirectoryWithoutDirectoryOpts, + ) -> Directory { + let mut query = self.selection.select("withoutDirectory"); + query = query.arg("path", path.into()); + if let Some(allow_wild_card) = opts.allow_wild_card { + query = query.arg("allowWildCard", allow_wild_card); + } + if let Some(allow_not_found) = opts.allow_not_found { + query = query.arg("allowNotFound", allow_not_found); + } + Directory { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } /// Retrieves this directory with the file at the given path removed. /// /// # Arguments /// /// * `path` - Location of the file to remove (e.g., "/file.txt"). + /// * `opt` - optional argument, see inner type for documentation, use _opts to use pub fn without_file(&self, path: impl Into) -> Directory { let mut query = self.selection.select("withoutFile"); query = query.arg("path", path.into()); @@ -3853,6 +3898,31 @@ impl Directory { graphql_client: self.graphql_client.clone(), } } + /// Retrieves this directory with the file at the given path removed. + /// + /// # Arguments + /// + /// * `path` - Location of the file to remove (e.g., "/file.txt"). + /// * `opt` - optional argument, see inner type for documentation, use _opts to use + pub fn without_file_opts( + &self, + path: impl Into, + opts: DirectoryWithoutFileOpts, + ) -> Directory { + let mut query = self.selection.select("withoutFile"); + query = query.arg("path", path.into()); + if let Some(allow_wild_card) = opts.allow_wild_card { + query = query.arg("allowWildCard", allow_wild_card); + } + if let Some(allow_not_found) = opts.allow_not_found { + query = query.arg("allowNotFound", allow_not_found); + } + Directory { + proc: self.proc.clone(), + selection: query, + graphql_client: self.graphql_client.clone(), + } + } } #[derive(Clone)] pub struct EnvVariable { diff --git a/sdk/typescript/api/client.gen.ts b/sdk/typescript/api/client.gen.ts index 2c6d8b32d2..ad929cfe86 100644 --- a/sdk/typescript/api/client.gen.ts +++ b/sdk/typescript/api/client.gen.ts @@ -585,6 +585,30 @@ export type DirectoryWithNewFileOpts = { permissions?: number } +export type DirectoryWithoutDirectoryOpts = { + /** + * Allow wildcards in the path (e.g., "*.txt"). + */ + allowWildCard?: boolean + + /** + * Allow the operation to not fail if the directory does not exist. + */ + allowNotFound?: boolean +} + +export type DirectoryWithoutFileOpts = { + /** + * Allow wildcards in the path (e.g., "*.txt"). + */ + allowWildCard?: boolean + + /** + * Allow the operation to not fail if the directory does not exist. + */ + allowNotFound?: boolean +} + /** * The `DirectoryID` scalar type represents an identifier for an object of type Directory. */ @@ -3265,14 +3289,19 @@ export class Directory extends BaseClient { /** * Retrieves this directory with the directory at the given path removed. * @param path Location of the directory to remove (e.g., ".github/"). + * @param opts.allowWildCard Allow wildcards in the path (e.g., "*.txt"). + * @param opts.allowNotFound Allow the operation to not fail if the directory does not exist. */ - withoutDirectory = (path: string): Directory => { + withoutDirectory = ( + path: string, + opts?: DirectoryWithoutDirectoryOpts, + ): Directory => { return new Directory({ queryTree: [ ...this._queryTree, { operation: "withoutDirectory", - args: { path }, + args: { path, ...opts }, }, ], ctx: this._ctx, @@ -3282,14 +3311,16 @@ export class Directory extends BaseClient { /** * Retrieves this directory with the file at the given path removed. * @param path Location of the file to remove (e.g., "/file.txt"). + * @param opts.allowWildCard Allow wildcards in the path (e.g., "*.txt"). + * @param opts.allowNotFound Allow the operation to not fail if the directory does not exist. */ - withoutFile = (path: string): Directory => { + withoutFile = (path: string, opts?: DirectoryWithoutFileOpts): Directory => { return new Directory({ queryTree: [ ...this._queryTree, { operation: "withoutFile", - args: { path }, + args: { path, ...opts }, }, ], ctx: this._ctx,