Skip to content

Commit 66abd65

Browse files
committed
Fix support for getting single files from GitHub
This currently fails for GitHub (and possibly other Git repositories) because there is an assumption in the "client.go" file that the "subDir" segment is always a directory. However, for this URL: https://github.com/owner/repo/subdir/file.txt The "subDir" variable would actually be a file ("subdir/file.txt") and hence the client should use "copyFile" rather than "copyDir" when copying the "subDir" to the final destination. This change fixes that to check whether the "subDir" variable is a file or a directory, and appropriately uses "copyDir" or "copyFile". It also returns the single file in the "GetResult" object, if indeed a single file was requested. Tests attached.
1 parent 31c3313 commit 66abd65

File tree

2 files changed

+113
-4
lines changed

2 files changed

+113
-4
lines changed

client.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"io"
78
"io/ioutil"
89
"os"
910
"path/filepath"
@@ -317,11 +318,43 @@ func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, *
317318
return nil, &getError{true, err}
318319
}
319320

320-
err = copyDir(ctx, req.realDst, subDir, false, req.DisableSymlinks, req.umask())
321-
if err != nil {
322-
return nil, &getError{false, err}
321+
if stat, err := os.Stat(subDir); err != nil {
322+
return nil, &getError{false, fmt.Errorf("failed to stat '%s': %w", subDir, err)}
323+
} else if stat.IsDir() {
324+
err = copyDir(ctx, req.realDst, subDir, false, req.DisableSymlinks, req.umask())
325+
if err != nil {
326+
return nil, &getError{false, err}
327+
}
328+
return &GetResult{req.realDst}, nil
329+
} else {
330+
src, err := os.Open(subDir)
331+
if err != nil {
332+
return nil, &getError{false, fmt.Errorf("failed to open local source file at '%s': %w", subDir, err)}
333+
}
334+
//goland:noinspection GoUnhandledErrorResult
335+
defer src.Close()
336+
337+
target := filepath.Join(req.realDst, filepath.Base(subDir))
338+
dst, err := os.Create(target)
339+
if err != nil {
340+
return nil, &getError{false, fmt.Errorf("failed to open local target file at '%s': %w", target, err)}
341+
}
342+
//goland:noinspection GoUnhandledErrorResult
343+
defer dst.Close()
344+
345+
buf := make([]byte, 1024*20) // 20k buffer should usually suffice for 99% of files
346+
for {
347+
n, err := src.Read(buf)
348+
if err != nil && err != io.EOF {
349+
return nil, &getError{false, fmt.Errorf("failed to read local source file at '%s': %w", subDir, err)}
350+
} else if n == 0 {
351+
break
352+
} else if _, err := dst.Write(buf[:n]); err != nil {
353+
return nil, &getError{false, fmt.Errorf("failed to write to local source file at '%s': %w", target, err)}
354+
}
355+
}
356+
return &GetResult{target}, nil
323357
}
324-
return &GetResult{req.realDst}, nil
325358
}
326359

327360
return &GetResult{req.Dst}, nil

get_github_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package getter
2+
3+
import (
4+
"context"
5+
testing_helper "github.com/hashicorp/go-getter/v2/helper/testing"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
)
10+
11+
const basicMainTFExpectedContents = `# Hello
12+
13+
module "foo" {
14+
source = "./foo"
15+
}
16+
`
17+
18+
func TestGitGetter_githubDirWithModeAny(t *testing.T) {
19+
if !testHasGit {
20+
t.Skip("git not found, skipping")
21+
}
22+
23+
ctx := context.Background()
24+
dst := testing_helper.TempDir(t)
25+
defer os.RemoveAll(dst)
26+
27+
req := &Request{
28+
Src: "git::https://github.com/arikkfir/go-getter.git//testdata/basic?ref=v2",
29+
Dst: dst,
30+
GetMode: ModeAny,
31+
Copy: true,
32+
}
33+
client := Client{}
34+
result, err := client.Get(ctx, req)
35+
if err != nil {
36+
t.Fatalf("Failed fetching GitHub directory: %s", err)
37+
} else if stat, err := os.Stat(result.Dst); err != nil {
38+
t.Fatalf("Failed stat dst at '%s': %s", result.Dst, err)
39+
} else if !stat.IsDir() {
40+
t.Fatalf("Expected '%s' to be a directory", result.Dst)
41+
} else if entries, err := os.ReadDir(result.Dst); err != nil {
42+
t.Fatalf("Failed listing directory '%s': %s", result.Dst, err)
43+
} else if len(entries) != 3 {
44+
t.Fatalf("Expected dir '%s' to contain 3 items: %s", result.Dst, err)
45+
} else {
46+
testing_helper.AssertContents(t, filepath.Join(result.Dst, "main.tf"), basicMainTFExpectedContents)
47+
}
48+
}
49+
50+
func TestGitGetter_githubFileWithModeAny(t *testing.T) {
51+
if !testHasGit {
52+
t.Skip("git not found, skipping")
53+
}
54+
55+
ctx := context.Background()
56+
dst := testing_helper.TempDir(t)
57+
defer os.RemoveAll(dst)
58+
59+
req := &Request{
60+
Src: "git::https://github.com/arikkfir/go-getter.git//testdata/basic/main.tf?ref=v2",
61+
Dst: dst,
62+
GetMode: ModeAny,
63+
Copy: true,
64+
}
65+
client := Client{}
66+
result, err := client.Get(ctx, req)
67+
if err != nil {
68+
t.Fatalf("Failed fetching GitHub file: %s", err)
69+
} else if stat, err := os.Stat(result.Dst); err != nil {
70+
t.Fatalf("Failed stat dst at '%s': %s", result.Dst, err)
71+
} else if stat.IsDir() {
72+
t.Fatalf("Expected '%s' to be a file", result.Dst)
73+
} else {
74+
testing_helper.AssertContents(t, result.Dst, basicMainTFExpectedContents)
75+
}
76+
}

0 commit comments

Comments
 (0)