Skip to content

Commit

Permalink
core: add support for wipe arg to Directory.export
Browse files Browse the repository at this point in the history
By default, exporting a directory resulted in its contents being merged
into the target directory on the host, with any files that existed on
the host but not in the source dir being untouched during the export.

More often than not, this is the behavior you want, but there are use
cases where you'd instead like to replace the contents of the host
directory entirely such that it exactly matches the source dir being
exported. This enables you to e.g. load a dir from the host, delete some
files from it and then export that back to the host with those deletes
being reflected.

This change adds an arg to `Directory.export` to enable that behavior.

I really wanted to name the arg `merge` and default it to `true`, but it
turns out that doesn't really work for optional args since at least the
Go SDK can't distinguish between an explicit `false` value on an
optional arg and an explicitly set `false`, so it wasn't possible to
ever get anything but the `merge: true` behavior. Didn't check other
SDKs but I would imagine at least a few of them have the same sort of
problem.

So I fell back to just naming the arg `wipe` and making it trigger
the non-default behavior only when set to true.

Signed-off-by: Erik Sipsma <erik@dagger.io>
  • Loading branch information
sipsma committed Mar 21, 2024
1 parent 5974f19 commit c348df6
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 14 deletions.
4 changes: 2 additions & 2 deletions core/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ func (dir *Directory) Without(ctx context.Context, path string) (*Directory, err
return dir, nil
}

func (dir *Directory) Export(ctx context.Context, destPath string) (rerr error) {
func (dir *Directory) Export(ctx context.Context, destPath string, merge bool) (rerr error) {
svcs := dir.Query.Services
bk := dir.Query.Buildkit

Expand Down Expand Up @@ -735,7 +735,7 @@ func (dir *Directory) Export(ctx context.Context, destPath string) (rerr error)
}
defer detach()

return bk.LocalDirExport(ctx, defPB, destPath)
return bk.LocalDirExport(ctx, defPB, destPath, merge)
}

// Root removes any relative path from the directory.
Expand Down
20 changes: 20 additions & 0 deletions core/integration/directory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,26 @@ func TestDirectoryExport(t *testing.T) {
entries, err := ls(wd)
require.NoError(t, err)
require.Equal(t, []string{"20locale.sh", "README", "color_prompt.sh.disabled"}, entries)

t.Run("wipe flag", func(t *testing.T) {
dir := dir.WithoutFile("README")

// by default a delete in the source dir won't overwrite the destination on the host
ok, err := dir.Export(ctx, ".")
require.NoError(t, err)
require.True(t, ok)
entries, err = ls(wd)
require.NoError(t, err)
require.Equal(t, []string{"20locale.sh", "README", "color_prompt.sh.disabled"}, entries)

// wipe results in the destination being replaced with the source entirely, including deletes
ok, err = dir.Export(ctx, ".", dagger.DirectoryExportOpts{Wipe: true})
require.NoError(t, err)
require.True(t, ok)
entries, err = ls(wd)
require.NoError(t, err)
require.Equal(t, []string{"20locale.sh", "color_prompt.sh.disabled"}, entries)
})
})

t.Run("to outer dir", func(t *testing.T) {
Expand Down
6 changes: 4 additions & 2 deletions core/schema/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ func (s *directorySchema) Install() {
dagql.Func("export", s.export).
Impure("Writes to the local host.").
Doc(`Writes the contents of the directory to a path on the host.`).
ArgDoc("path", `Location of the copied directory (e.g., "logs/").`),
ArgDoc("path", `Location of the copied directory (e.g., "logs/").`).
ArgDoc("wipe", `If true, then the host directory will be wiped clean such that it exactly matches the directory being exported, including deleting any files on the host that aren't in the exported dir. If false (the default), the contents of the directory will be merged with any existing contents of the host directory, leaving any existing files on the host that aren't in the exported directory alone.`),
dagql.Func("dockerBuild", s.dockerBuild).
Doc(`Builds a new Docker container from this directory.`).
ArgDoc("dockerfile", `Path to the Dockerfile to use (e.g., "frontend.Dockerfile").`).
Expand Down Expand Up @@ -262,10 +263,11 @@ func (s *directorySchema) diff(ctx context.Context, parent *core.Directory, args

type dirExportArgs struct {
Path string
Wipe bool `default:"false"`
}

func (s *directorySchema) export(ctx context.Context, parent *core.Directory, args dirExportArgs) (dagql.Boolean, error) {
err := parent.Export(ctx, args.Path)
err := parent.Export(ctx, args.Path, !args.Wipe)
if err != nil {
return false, err
}
Expand Down
10 changes: 10 additions & 0 deletions docs/docs-graphql/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,16 @@ type Directory {
export(
"""Location of the copied directory (e.g., "logs/")."""
path: String!

"""
If true, then the host directory will be wiped clean such that it exactly
matches the directory being exported, including deleting any files on the
host that aren't in the exported dir. If false (the default), the contents
of the directory will be merged with any existing contents of the host
directory, leaving any existing files on the host that aren't in the
exported directory alone.
"""
wipe: Boolean = false
): Boolean!

"""Retrieves a file at the given path."""
Expand Down
4 changes: 3 additions & 1 deletion engine/buildkit/filesync.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ func (c *Client) LocalDirExport(
ctx context.Context,
def *bksolverpb.Definition,
destPath string,
merge bool,
) (rerr error) {
ctx = bklog.WithLogger(ctx, bklog.G(ctx).WithField("export_path", destPath))
bklog.G(ctx).Debug("exporting local dir")
Expand Down Expand Up @@ -226,7 +227,8 @@ func (c *Client) LocalDirExport(
}

ctx = engine.LocalExportOpts{
Path: destPath,
Path: destPath,
Merge: merge,
}.AppendToOutgoingContext(ctx)

_, descRef, err := expInstance.Export(ctx, cacheRes, nil, clientMetadata.ClientID)
Expand Down
2 changes: 1 addition & 1 deletion engine/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ func (AnyDirTarget) DiffCopy(stream filesync.FileSend_DiffCopyServer) (rerr erro
}

err := fsutil.Receive(stream.Context(), stream, opts.Path, fsutil.ReceiveOpt{
Merge: true,
Merge: opts.Merge,
Filter: func(path string, stat *fstypes.Stat) bool {
stat.Uid = uint32(os.Getuid())
stat.Gid = uint32(os.Getgid())
Expand Down
4 changes: 4 additions & 0 deletions engine/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ type LocalExportOpts struct {
FileOriginalName string `json:"file_original_name"`
AllowParentDirPath bool `json:"allow_parent_dir_path"`
FileMode os.FileMode `json:"file_mode"`
// whether to just merge in contents of a directory to the target on the host
// or to replace the target entirely such that it matches the source directory,
// which includes deleting any files that are not in the source directory
Merge bool
}

func (o LocalExportOpts) ToGRPCMD() metadata.MD {
Expand Down
15 changes: 12 additions & 3 deletions sdk/elixir/lib/dagger/gen/directory.ex

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion sdk/go/dagger.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion sdk/php/generated/Directory.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion sdk/python/src/dagger/client/gen.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions sdk/rust/crates/dagger-sdk/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2624,6 +2624,12 @@ pub struct DirectoryEntriesOpts<'a> {
pub path: Option<&'a str>,
}
#[derive(Builder, Debug, PartialEq)]
pub struct DirectoryExportOpts {
/// If true, then the host directory will be wiped clean such that it exactly matches the directory being exported, including deleting any files on the host that aren't in the exported dir. If false (the default), the contents of the directory will be merged with any existing contents of the host directory, leaving any existing files on the host that aren't in the exported directory alone.
#[builder(setter(into, strip_option), default)]
pub wipe: Option<bool>,
}
#[derive(Builder, Debug, PartialEq)]
pub struct DirectoryPipelineOpts<'a> {
/// Description of the sub-pipeline.
#[builder(setter(into, strip_option), default)]
Expand Down Expand Up @@ -2799,11 +2805,30 @@ impl Directory {
/// # Arguments
///
/// * `path` - Location of the copied directory (e.g., "logs/").
/// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use
pub async fn export(&self, path: impl Into<String>) -> Result<bool, DaggerError> {
let mut query = self.selection.select("export");
query = query.arg("path", path.into());
query.execute(self.graphql_client.clone()).await
}
/// Writes the contents of the directory to a path on the host.
///
/// # Arguments
///
/// * `path` - Location of the copied directory (e.g., "logs/").
/// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use
pub async fn export_opts(
&self,
path: impl Into<String>,
opts: DirectoryExportOpts,
) -> Result<bool, DaggerError> {
let mut query = self.selection.select("export");
query = query.arg("path", path.into());
if let Some(wipe) = opts.wipe {
query = query.arg("wipe", wipe);
}
query.execute(self.graphql_client.clone()).await
}
/// Retrieves a file at the given path.
///
/// # Arguments
Expand Down
15 changes: 13 additions & 2 deletions sdk/typescript/api/client.gen.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c348df6

Please sign in to comment.