diff --git a/internal/git_clone_test.go b/internal/git_clone_test.go index a4b662779..10d3269af 100644 --- a/internal/git_clone_test.go +++ b/internal/git_clone_test.go @@ -1,6 +1,8 @@ package internal import ( + "context" + "os" "path/filepath" "testing" @@ -8,14 +10,51 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAccGitClonePrivateRepository(t *testing.T) { +// TODO: add assertion that the git tool is not called, maybe by voiding PATH +// TODO: add assertion for error if git CLI is not found +func TestGitClonePublicRepository(t *testing.T) { + // t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV")) + tmpDir := t.TempDir() + ctx := context.Background() + var err error + + // We unset PATH to ensure that git.Clone cannot rely on the git CLI + t.Setenv("PATH", "") + + err = git.Clone(ctx, git.CloneOptions{ + Provider: "github", + Organization: "databricks", + RepositoryName: "cli", + Reference: "main", + TargetDir: tmpDir, + }) + + assert.NoError(t, err) + assert.DirExists(t, filepath.Join(tmpDir, "cli-main")) + + b, err := os.ReadFile(filepath.Join(tmpDir, "cli-main/NOTICE")) + assert.NoError(t, err) + assert.Contains(t, string(b), "Copyright (2023) Databricks, Inc.") +} + +func TestAccGitClonePrivateRepository(t *testing.T) { + // t.Log(GetEnvOrSkipTest(t, "CLOUD_ENV")) + + tmpDir := t.TempDir() + ctx := context.Background() // This is a private repository only accessible to databricks employees - err := git.Clone("databricks", "bundle-samples-internal", tmpDir) + err := git.Clone(ctx, git.CloneOptions{ + Provider: "github", + Organization: "databricks", + RepositoryName: "bundle-samples-internal", + Reference: "main", + TargetDir: tmpDir, + }) assert.NoError(t, err) // assert examples from the private repository - assert.DirExists(t, filepath.Join(tmpDir, "shark_sightings")) - assert.DirExists(t, filepath.Join(tmpDir, "wikipedia_clickstream")) + assert.DirExists(t, filepath.Join(tmpDir, "bundle-samples-internal-main", "shark_sightings")) + assert.DirExists(t, filepath.Join(tmpDir, "bundle-samples-internal-main", "wikipedia_clickstream")) } diff --git a/libs/git/clone.go b/libs/git/clone.go index 7c191bdab..837b48988 100644 --- a/libs/git/clone.go +++ b/libs/git/clone.go @@ -1,6 +1,7 @@ package git import ( + "context" "errors" "fmt" "io" @@ -14,14 +15,6 @@ import ( var errNotFound = errors.New("not found") -func githubZipUrl(org string, name string, ref string) string { - return fmt.Sprintf(`%s/archive/%s.zip`, githubUrl(org, name), ref) -} - -func githubUrl(org string, name string) string { - return fmt.Sprintf(`https://github.com/%s/%s`, org, name) -} - type RepositoryNotFoundError struct { url string } @@ -34,9 +27,42 @@ func (err RepositoryNotFoundError) Is(other error) bool { return other == errNotFound } -// TODO: pass context to these the get requests -func download(url string, dest string) error { - resp, err := http.Get(url) +type CloneOptions struct { + // Name of the organization or profile with the repository + Organization string + RepositoryName string + + // Git service provider. Eg: github, gitlab + Provider string + + // Branch or tag name to clone + Reference string + + // Path to clone into. The repository is cloned as ${RepositoryName}-${Reference} + // in this target directory. + TargetDir string +} + +func (opts CloneOptions) repoUrl() string { + return fmt.Sprintf(`https://github.com/%s/%s`, opts.Organization, opts.RepositoryName) +} + +func (opts CloneOptions) zipUrl() string { + return fmt.Sprintf(`%s/archive/%s.zip`, opts.repoUrl(), opts.Reference) +} + +// TODO: test this holds true for alternate branches and tags +func (opts CloneOptions) destination() string { + return filepath.Join(opts.TargetDir, opts.RepositoryName+"-"+opts.Reference) +} + +func download(ctx context.Context, url string, dest string) error { + // Get request to download the ZIP archive + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return err + } + resp, err := http.DefaultClient.Do(request) if err != nil { return err } @@ -59,45 +85,45 @@ func download(url string, dest string) error { return err } -func clonePrivate(org string, repoName string, targetDir string) error { - zipUrl := githubUrl(org, repoName) - - // We append repoName-main to targetDir to be symmetric to clonePublic - targetDir = filepath.Join(targetDir, repoName+"-main") - - // TODO: pass context to the command execution - cmd := exec.Command("git", "clone", zipUrl, targetDir, "--branch", "main") +// TODO: check stdin / stdout works properly with git clone and requesting an ID/password +func clonePrivate(ctx context.Context, opts CloneOptions) error { + // TODO: test that the branch --branch flag works with tags + cmd := exec.CommandContext(ctx, "git", "clone", opts.repoUrl(), opts.destination(), "--branch", opts.Reference) return cmd.Run() } -func clonePublic(org string, repoName string, targetDir string) error { - zipDst := filepath.Join(targetDir, repoName+".zip") - zipUrl := githubZipUrl(org, repoName, "main") +func clonePublic(ctx context.Context, opts CloneOptions) error { + zipDst := filepath.Join(opts.TargetDir, opts.RepositoryName+".zip") // Download public repository from github as a ZIP file - err := download(zipUrl, zipDst) + err := download(ctx, opts.zipUrl(), zipDst) if err != nil { return err } // Decompress the ZIP file - err = zip.Extract(zipDst, targetDir) + err = zip.Extract(zipDst, opts.TargetDir) if err != nil { return err } - // Remove the ZIP file + // Remove the ZIP file post extraction return os.Remove(zipDst) } -func Clone(org, repoName string, targetDir string) error { - // First we try to clone the repository as a public URL, as that does not - // require the git CLI - err := clonePublic(org, repoName, targetDir) - if err != nil && !errors.Is(err, errNotFound) { - return err +func Clone(ctx context.Context, opts CloneOptions) error { + if opts.Provider != "github" { + return fmt.Errorf("git provider not supported: %s", opts.Provider) } - // Since a public repository was not found, we defer to the git CLI - return clonePrivate(org, repoName, targetDir) + // First we try to clone the repository as a public URL, as that does not + // require the git CLI + err := clonePublic(ctx, opts) + + // If a public repository was not found, we defer to the git CLI + if errors.Is(err, errNotFound) { + + return clonePrivate(ctx, opts) + } + return err } diff --git a/libs/git/clone_test.go b/libs/git/clone_test.go deleted file mode 100644 index a28e43110..000000000 --- a/libs/git/clone_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package git - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGitClonePublicRepository(t *testing.T) { - tmpDir := t.TempDir() - var err error - - err = clonePublic("databricks", "cli", tmpDir) - assert.NoError(t, err) - assert.DirExists(t, filepath.Join(tmpDir, "cli-main")) - - b, err := os.ReadFile(filepath.Join(tmpDir, "cli-main/NOTICE")) - assert.NoError(t, err) - assert.Contains(t, string(b), "Copyright (2023) Databricks, Inc.") -}