Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: add support for wipe arg to Directory.export #6909

Merged
merged 2 commits into from Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions core/directory.go
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
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
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 before exporting so that it exactly matches the directory being exported; this means it will delete 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
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 before exporting so
that it exactly matches the directory being exported; this means it will
delete 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
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, so this defaulted to 'delete everything' before? I've had files mysteriously disappear recently, maybe this expains it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No the behavior before was to not delete any files, that's unchanged here. So something else must have been afoot 🔎

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
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
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.

16 changes: 15 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
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 before exporting so that it exactly matches the directory being exported; this means it will delete 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.