mirror of https://github.com/databricks/cli.git
Allow specifying repo by path for repos commands (#526)
## Changes The repos commands take a repo ID. It is more convenient to specify repos by their paths. Closes #523. ## Tests New integration tests pass.
This commit is contained in:
parent
cf92698eb3
commit
64fcd3d2ee
|
@ -1,9 +1,126 @@
|
||||||
package repos
|
package repos
|
||||||
|
|
||||||
import "github.com/databricks/cli/libs/cmdio"
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/cmd/root"
|
||||||
|
"github.com/databricks/cli/libs/cmdio"
|
||||||
|
"github.com/databricks/databricks-sdk-go"
|
||||||
|
"github.com/databricks/databricks-sdk-go/service/workspace"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
listCmd.Annotations["template"] = cmdio.Heredoc(`
|
listCmd.Annotations["template"] = cmdio.Heredoc(`
|
||||||
{{range .}}{{green "%d" .Id}} {{.Path}} {{.Branch|blue}} {{.Url|cyan}}
|
{{range .}}{{green "%d" .Id}} {{.Path}} {{.Branch|blue}} {{.Url|cyan}}
|
||||||
{{end}}`)
|
{{end}}`)
|
||||||
|
|
||||||
|
deleteCmd.Use = "delete REPO_ID_OR_PATH"
|
||||||
|
deleteCmd.RunE = func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
w := root.WorkspaceClient(ctx)
|
||||||
|
if cmd.Flags().Changed("json") {
|
||||||
|
err = deleteJson.Unmarshal(&deleteReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deleteReq.RepoId, err = repoArgumentToRepoID(ctx, w, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = w.Repos.Delete(ctx, deleteReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
getCmd.Use = "get REPO_ID_OR_PATH"
|
||||||
|
getCmd.RunE = func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
w := root.WorkspaceClient(ctx)
|
||||||
|
if cmd.Flags().Changed("json") {
|
||||||
|
err = getJson.Unmarshal(&getReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getReq.RepoId, err = repoArgumentToRepoID(ctx, w, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := w.Repos.Get(ctx, getReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cmdio.Render(ctx, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCmd.Use = "update REPO_ID_OR_PATH"
|
||||||
|
updateCmd.RunE = func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
w := root.WorkspaceClient(ctx)
|
||||||
|
if cmd.Flags().Changed("json") {
|
||||||
|
err = updateJson.Unmarshal(&updateReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateReq.RepoId, err = repoArgumentToRepoID(ctx, w, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Repos.Update(ctx, updateReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoArgumentToRepoID(ctx context.Context, w *databricks.WorkspaceClient, args []string) (int64, error) {
|
||||||
|
// ---- Begin copy from cmd/workspace/repos/repos.go ----
|
||||||
|
if len(args) == 0 {
|
||||||
|
promptSpinner := cmdio.Spinner(ctx)
|
||||||
|
promptSpinner <- "No REPO_ID argument specified. Loading names for Repos drop-down."
|
||||||
|
names, err := w.Repos.RepoInfoPathToIdMap(ctx, workspace.ListReposRequest{})
|
||||||
|
close(promptSpinner)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to load names for Repos drop-down. Please manually specify required arguments. Original error: %w", err)
|
||||||
|
}
|
||||||
|
id, err := cmdio.Select(ctx, names, "The ID for the corresponding repo to access")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
args = append(args, id)
|
||||||
|
}
|
||||||
|
if len(args) != 1 {
|
||||||
|
return 0, fmt.Errorf("expected to have the id for the corresponding repo to access")
|
||||||
|
}
|
||||||
|
// ---- End copy from cmd/workspace/repos/repos.go ----
|
||||||
|
|
||||||
|
// If the argument is a repo ID, return it.
|
||||||
|
arg := args[0]
|
||||||
|
id, err := strconv.ParseInt(arg, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the argument cannot be parsed as a repo ID, try to look it up by name.
|
||||||
|
oi, err := w.Workspace.GetStatusByPath(ctx, arg)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to look up repo by path: %w", err)
|
||||||
|
}
|
||||||
|
if oi.ObjectType != workspace.ObjectTypeRepo {
|
||||||
|
return 0, fmt.Errorf("object at path %q is not a repo", arg)
|
||||||
|
}
|
||||||
|
return oi.ObjectId, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/databricks/databricks-sdk-go"
|
||||||
|
"github.com/databricks/databricks-sdk-go/apierr"
|
||||||
|
"github.com/databricks/databricks-sdk-go/service/workspace"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTemporaryRepo(t *testing.T, w *databricks.WorkspaceClient, ctx context.Context) (int64, string) {
|
||||||
|
me, err := w.CurrentUser.Me(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
repoPath := fmt.Sprintf("/Repos/%s/%s", me.UserName, RandomName("empty-repo-integration-"))
|
||||||
|
repoInfo, err := w.Repos.Create(ctx, workspace.CreateRepo{
|
||||||
|
Path: repoPath,
|
||||||
|
Url: repoUrl,
|
||||||
|
Provider: "gitHub",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := w.Repos.DeleteByRepoId(ctx, repoInfo.Id)
|
||||||
|
if !apierr.IsMissing(err) {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return repoInfo.Id, repoPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReposGet(t *testing.T) {
|
||||||
|
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
w, err := databricks.NewWorkspaceClient()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
repoId, repoPath := createTemporaryRepo(t, w, ctx)
|
||||||
|
|
||||||
|
// Get by ID
|
||||||
|
byIdOutput, stderr := RequireSuccessfulRun(t, "repos", "get", strconv.FormatInt(repoId, 10), "--output=json")
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
// Get by path
|
||||||
|
byPathOutput, stderr := RequireSuccessfulRun(t, "repos", "get", repoPath, "--output=json")
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
// Output should be the same
|
||||||
|
assert.Equal(t, byIdOutput.String(), byPathOutput.String())
|
||||||
|
|
||||||
|
// Get by path fails
|
||||||
|
_, stderr, err = RequireErrorRun(t, "repos", "get", repoPath+"-doesntexist", "--output=json")
|
||||||
|
assert.ErrorContains(t, err, "failed to look up repo")
|
||||||
|
|
||||||
|
// Get by path resolves to something other than a repo
|
||||||
|
_, stderr, err = RequireErrorRun(t, "repos", "get", "/Repos", "--output=json")
|
||||||
|
assert.ErrorContains(t, err, "is not a repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReposUpdate(t *testing.T) {
|
||||||
|
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
w, err := databricks.NewWorkspaceClient()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
repoId, repoPath := createTemporaryRepo(t, w, ctx)
|
||||||
|
|
||||||
|
// Update by ID
|
||||||
|
byIdOutput, stderr := RequireSuccessfulRun(t, "repos", "update", strconv.FormatInt(repoId, 10), "--branch", "ide")
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
// Update by path
|
||||||
|
byPathOutput, stderr := RequireSuccessfulRun(t, "repos", "update", repoPath, "--branch", "ide")
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
// Output should be the same
|
||||||
|
assert.Equal(t, byIdOutput.String(), byPathOutput.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReposDeleteByID(t *testing.T) {
|
||||||
|
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
w, err := databricks.NewWorkspaceClient()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
repoId, _ := createTemporaryRepo(t, w, ctx)
|
||||||
|
|
||||||
|
// Delete by ID
|
||||||
|
stdout, stderr := RequireSuccessfulRun(t, "repos", "delete", strconv.FormatInt(repoId, 10))
|
||||||
|
assert.Equal(t, "", stdout.String())
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
// Check it was actually deleted
|
||||||
|
_, err = w.Repos.GetByRepoId(ctx, repoId)
|
||||||
|
assert.True(t, apierr.IsMissing(err), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReposDeleteByPath(t *testing.T) {
|
||||||
|
t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV"))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
w, err := databricks.NewWorkspaceClient()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
repoId, repoPath := createTemporaryRepo(t, w, ctx)
|
||||||
|
|
||||||
|
// Delete by path
|
||||||
|
stdout, stderr := RequireSuccessfulRun(t, "repos", "delete", repoPath)
|
||||||
|
assert.Equal(t, "", stdout.String())
|
||||||
|
assert.Equal(t, "", stderr.String())
|
||||||
|
|
||||||
|
// Check it was actually deleted
|
||||||
|
_, err = w.Repos.GetByRepoId(ctx, repoId)
|
||||||
|
assert.True(t, apierr.IsMissing(err), err)
|
||||||
|
}
|
Loading…
Reference in New Issue