diff --git a/cmd/workspace/repos/overrides.go b/cmd/workspace/repos/overrides.go index e3b58857..9f131c03 100644 --- a/cmd/workspace/repos/overrides.go +++ b/cmd/workspace/repos/overrides.go @@ -1,9 +1,126 @@ 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() { listCmd.Annotations["template"] = cmdio.Heredoc(` {{range .}}{{green "%d" .Id}} {{.Path}} {{.Branch|blue}} {{.Url|cyan}} {{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 } diff --git a/internal/repos_test.go b/internal/repos_test.go new file mode 100644 index 00000000..957952ca --- /dev/null +++ b/internal/repos_test.go @@ -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) +}