diff --git a/.nextchanges/cli/6118.md b/.nextchanges/cli/6118.md new file mode 100644 index 00000000000..183b98f91d8 --- /dev/null +++ b/.nextchanges/cli/6118.md @@ -0,0 +1 @@ +Support `dbfs:/Skills/...` paths in `databricks fs` commands, routed to the Files API. diff --git a/acceptance/cmd/fs/cp/file-to-skill/local.txt b/acceptance/cmd/fs/cp/file-to-skill/local.txt new file mode 100644 index 00000000000..a0423896973 --- /dev/null +++ b/acceptance/cmd/fs/cp/file-to-skill/local.txt @@ -0,0 +1 @@ +hello world! diff --git a/acceptance/cmd/fs/cp/file-to-skill/out.test.toml b/acceptance/cmd/fs/cp/file-to-skill/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/fs/cp/file-to-skill/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/fs/cp/file-to-skill/output.txt b/acceptance/cmd/fs/cp/file-to-skill/output.txt new file mode 100644 index 00000000000..187850bdf86 --- /dev/null +++ b/acceptance/cmd/fs/cp/file-to-skill/output.txt @@ -0,0 +1,6 @@ + +>>> [CLI] fs cp local.txt dbfs:/Skills/main/default/fs-cp-test-[UNIQUE_NAME]/README.md +local.txt -> dbfs:/Skills/main/default/fs-cp-test-[UNIQUE_NAME]/README.md + +>>> [CLI] fs cat dbfs:/Skills/main/default/fs-cp-test-[UNIQUE_NAME]/README.md +hello world! diff --git a/acceptance/cmd/fs/cp/file-to-skill/script b/acceptance/cmd/fs/cp/file-to-skill/script new file mode 100644 index 00000000000..20c53986072 --- /dev/null +++ b/acceptance/cmd/fs/cp/file-to-skill/script @@ -0,0 +1,13 @@ +CATALOG_NAME="main" +SCHEMA_NAME="default" +SKILL_NAME="fs-cp-test-${UNIQUE_NAME}" + +# Create target directory. UC Skills are served by the Files API, so unlike the +# Volumes tests there is no catalog object to create first. +$CLI fs mkdir dbfs:/Skills/${CATALOG_NAME}/${SCHEMA_NAME}/${SKILL_NAME} + +# Upload a local file into the skill bundle. +trace $CLI fs cp local.txt dbfs:/Skills/${CATALOG_NAME}/${SCHEMA_NAME}/${SKILL_NAME}/README.md + +# Verify file was uploaded correctly. +trace $CLI fs cat dbfs:/Skills/${CATALOG_NAME}/${SCHEMA_NAME}/${SKILL_NAME}/README.md diff --git a/acceptance/cmd/fs/cp/file-to-skill/test.toml b/acceptance/cmd/fs/cp/file-to-skill/test.toml new file mode 100644 index 00000000000..c40adb46488 --- /dev/null +++ b/acceptance/cmd/fs/cp/file-to-skill/test.toml @@ -0,0 +1,4 @@ +# Local only: the UC Skills Files API path is gated by a server-side feature flag +# that has not rolled out, so a real workspace rejects these writes. +Local = true +Cloud = false diff --git a/cmd/fs/cat.go b/cmd/fs/cat.go index 28df80d70df..c9d23c99d57 100644 --- a/cmd/fs/cat.go +++ b/cmd/fs/cat.go @@ -10,7 +10,7 @@ func newCatCommand() *cobra.Command { cmd := &cobra.Command{ Use: "cat FILE_PATH", Short: "Show file content.", - Long: `Show the contents of a file in DBFS or a UC Volume.`, + Long: `Show the contents of a file in DBFS, a UC Volume or a UC Skill.`, Args: root.ExactArgs(1), PreRunE: root.MustWorkspaceClient, } diff --git a/cmd/fs/cp.go b/cmd/fs/cp.go index 01d566cd4a9..734faaeb996 100644 --- a/cmd/fs/cp.go +++ b/cmd/fs/cp.go @@ -210,9 +210,9 @@ func newCpCommand() *cobra.Command { cmd := &cobra.Command{ Use: "cp SOURCE_PATH TARGET_PATH", Short: "Copy files and directories.", - Long: `Copy files and directories to and from any paths on DBFS, UC Volumes or your local filesystem. + Long: `Copy files and directories to and from any paths on DBFS, UC Volumes, UC Skills or your local filesystem. - For paths in DBFS and UC Volumes, it is required that you specify the "dbfs" scheme. + For paths in DBFS, UC Volumes and UC Skills, it is required that you specify the "dbfs" scheme. For example: dbfs:/foo/bar. Recursively copying a directory will copy all files inside directory diff --git a/cmd/fs/fs.go b/cmd/fs/fs.go index 1f36696a65a..074bd31a64c 100644 --- a/cmd/fs/fs.go +++ b/cmd/fs/fs.go @@ -8,7 +8,7 @@ func New() *cobra.Command { cmd := &cobra.Command{ Use: "fs", Short: "Filesystem related commands", - Long: `Commands to do file system operations on DBFS and UC Volumes.`, + Long: `Commands to do file system operations on DBFS, UC Volumes and UC Skills.`, GroupID: "workspace", } diff --git a/cmd/fs/helpers.go b/cmd/fs/helpers.go index 42b3e546fe5..d2e98563314 100644 --- a/cmd/fs/helpers.go +++ b/cmd/fs/helpers.go @@ -38,8 +38,8 @@ func filerForPath(ctx context.Context, fullPath string) (filer.Filer, string, er path := parts[1] w := cmdctx.WorkspaceClient(ctx) - // If the specified path has the "Volumes" prefix, use the Files API. - if strings.HasPrefix(path, "/Volumes/") { + // If the specified path has the "Volumes" or "Skills" prefix, use the Files API. + if strings.HasPrefix(path, "/Volumes/") || strings.HasPrefix(path, "/Skills/") { f, err := filer.NewFilesClient(ctx, w, "/") return f, path, err } diff --git a/cmd/fs/helpers_test.go b/cmd/fs/helpers_test.go index d0ccdd4a209..ee1a88e23fb 100644 --- a/cmd/fs/helpers_test.go +++ b/cmd/fs/helpers_test.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/fakefs" "github.com/databricks/cli/libs/filer" + databrickscfg "github.com/databricks/databricks-sdk-go/config" "github.com/databricks/databricks-sdk-go/experimental/mocks" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" @@ -41,6 +42,31 @@ func TestFilerForPathForInvalidScheme(t *testing.T) { assert.ErrorContains(t, err, "invalid scheme") } +func TestFilerForPathForDbfsScheme(t *testing.T) { + tcases := []struct { + fullPath string + path string + filer filer.Filer + }{ + {"dbfs:/Volumes/foo/bar/baz", "/Volumes/foo/bar/baz", &filer.FilesClient{}}, + {"dbfs:/Skills/foo/bar/baz", "/Skills/foo/bar/baz", &filer.FilesClient{}}, + {"dbfs:/foo/bar/baz", "/foo/bar/baz", &filer.DbfsClient{}}, + } + + for _, tc := range tcases { + t.Run(tc.fullPath, func(t *testing.T) { + m := mocks.NewMockWorkspaceClient(t) + m.WorkspaceClient.Config = &databrickscfg.Config{} + ctx := cmdctx.SetWorkspaceClient(t.Context(), m.WorkspaceClient) + + f, path, err := filerForPath(ctx, tc.fullPath) + require.NoError(t, err) + assert.Equal(t, tc.path, path) + assert.IsType(t, tc.filer, f) + }) + } +} + func testWindowsFilerForPath(t *testing.T, ctx context.Context, fullPath string) { f, path, err := filerForPath(ctx, fullPath) assert.NoError(t, err) diff --git a/cmd/fs/ls.go b/cmd/fs/ls.go index 1e856a35e8d..c6151b8a27a 100644 --- a/cmd/fs/ls.go +++ b/cmd/fs/ls.go @@ -42,7 +42,7 @@ func newLsCommand() *cobra.Command { cmd := &cobra.Command{ Use: "ls DIR_PATH", Short: "Lists files.", - Long: `Lists files in DBFS and UC Volumes.`, + Long: `Lists files in DBFS, UC Volumes and UC Skills.`, Args: root.ExactArgs(1), PreRunE: root.MustWorkspaceClient, } diff --git a/cmd/fs/mkdir.go b/cmd/fs/mkdir.go index 5e9ac784292..15faaad1569 100644 --- a/cmd/fs/mkdir.go +++ b/cmd/fs/mkdir.go @@ -12,7 +12,7 @@ func newMkdirCommand() *cobra.Command { // is called databricks fs mkdirs in our legacy CLI: https://github.com/databricks/databricks-cli Aliases: []string{"mkdirs"}, Short: "Make directories.", - Long: `Make directories in DBFS and UC Volumes. Mkdir will create directories along the path to the argument directory.`, + Long: `Make directories in DBFS, UC Volumes and UC Skills. Mkdir will create directories along the path to the argument directory.`, Args: root.ExactArgs(1), PreRunE: root.MustWorkspaceClient, } diff --git a/cmd/fs/rm.go b/cmd/fs/rm.go index a133a83097b..8855e528804 100644 --- a/cmd/fs/rm.go +++ b/cmd/fs/rm.go @@ -10,7 +10,7 @@ func newRmCommand() *cobra.Command { cmd := &cobra.Command{ Use: "rm PATH", Short: "Remove files and directories.", - Long: `Remove files and directories from DBFS and UC Volumes.`, + Long: `Remove files and directories from DBFS, UC Volumes and UC Skills.`, Args: root.ExactArgs(1), PreRunE: root.MustWorkspaceClient, }