Skip to content

Commit e2b6f4e

Browse files
committed
Simplify the options for configuring Github Enterprise and add version definition so older APIs can be supported.
1 parent 6bc76f6 commit e2b6f4e

File tree

3 files changed

+199
-40
lines changed

3 files changed

+199
-40
lines changed

ghe_v3.3.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-FileCopyrightText: 2022 Weston Schmidt <[email protected]>
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package githubfs
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"io/fs"
10+
"strings"
11+
)
12+
13+
// getGitDirV3_3 fetches a single directory via the github API. This isn't fast,
14+
// but there are conditions where it is advantageous over fetching everything
15+
// all at once.
16+
//
17+
// Github Enterprise v3.3 doesn't support size.
18+
func getGitDirV3_3(gfs *FS, d *dir) error {
19+
path := strings.Join(d.path, "/")
20+
21+
vars := map[string]any{
22+
"owner": d.org,
23+
"repo": d.repo,
24+
"exp": d.branch + ":" + path,
25+
}
26+
27+
/*
28+
query {
29+
repository(name: "repo", owner: "org") {
30+
object(expression: "main:") {
31+
... on Tree {
32+
entries {
33+
name
34+
mode
35+
}
36+
}
37+
}
38+
}
39+
}
40+
*/
41+
var query struct {
42+
Repository struct {
43+
Object struct {
44+
Tree struct {
45+
Entries []struct {
46+
Name string
47+
Mode int
48+
}
49+
} `graphql:"... on Tree"`
50+
} `graphql:"object(expression: $exp)"`
51+
} `graphql:"repository(name: $repo, owner: $owner)"`
52+
}
53+
54+
if err := gfs.gqlClient.Query(context.Background(), &query, vars); err != nil {
55+
return err
56+
}
57+
58+
for _, entry := range query.Repository.Object.Tree.Entries {
59+
url := strings.Join([]string{gfs.rawUrl, d.org, d.repo, d.branch, path, entry.Name}, "/")
60+
61+
switch entry.Mode {
62+
case ghModeFile:
63+
d.addFile(entry.Name, withUrl(url))
64+
case ghModeExecutable:
65+
d.addFile(entry.Name, withUrl(url), withMode(fs.FileMode(0755)))
66+
case ghModeDirectory:
67+
d.newDir(entry.Name, withFetcher(getGitDirV3_3))
68+
case ghModeSubmodule: // TODO
69+
case ghModeSymlink: // TODO
70+
default:
71+
return fmt.Errorf("unknown file mode")
72+
}
73+
}
74+
75+
return nil
76+
}

githubfs.go

+45-30
Original file line numberDiff line numberDiff line change
@@ -68,34 +68,20 @@ var _ fs.FS = (*FS)(nil)
6868

6969
// FS provides the githubfs
7070
type FS struct {
71-
httpClient *http.Client
72-
gqlClient *gql.Client
73-
connected bool
74-
githubUrl string
75-
rawUrl string
76-
inputs []input
77-
threshold int
78-
root *dir
71+
httpClient *http.Client
72+
gqlClient *gql.Client
73+
connected bool
74+
githubUrl string
75+
rawUrl string
76+
inputs []input
77+
threshold int
78+
root *dir
79+
getGitDirFn func(*FS, *dir) error
7980
}
8081

8182
// Option is the type used for options.
8283
type Option func(gfs *FS)
8384

84-
// WithURL provides a way to set the URL for the specific github instance to use.
85-
func WithGithubURL(url string) Option {
86-
return func(gfs *FS) {
87-
gfs.githubUrl = url
88-
}
89-
}
90-
91-
// WithRawURL provides a way to set the URL for downloading files via the web
92-
// interface vs. a tarball.
93-
func WithRawDownloadURL(url string) Option {
94-
return func(gfs *FS) {
95-
gfs.rawUrl = url
96-
}
97-
}
98-
9985
// WithHttpClient provides a way to set the HTTP client to use.
10086
func WithHttpClient(c *http.Client) Option {
10187
return func(gfs *FS) {
@@ -154,14 +140,43 @@ func WithThresholdInKB(max int) Option {
154140
}
155141
}
156142

143+
// WithGithubEnterprise specifies the API version to support for backwards
144+
// compatibility. The version value should be "3.3", "3.4", "3.5", "3.6", etc.
145+
// The baseURL passed in should look like this:
146+
//
147+
// http://github.company.com
148+
//
149+
// The needed paths will be added to the baseURL.
150+
//
151+
// GHEC should not use this option as it uses the public API and hosting.
152+
func WithGithubEnterprise(baseURL, version string) Option {
153+
switch version {
154+
default:
155+
return func(gfs *FS) {
156+
gfs.githubUrl = baseURL + "/api/graphql"
157+
gfs.rawUrl = baseURL + "/raw"
158+
gfs.getGitDirFn = getGitDirV3_3
159+
}
160+
}
161+
}
162+
163+
// withTestURL provides a way to inject a test url.
164+
func withTestURL(url string) Option {
165+
return func(gfs *FS) {
166+
gfs.githubUrl = url
167+
gfs.rawUrl = url
168+
}
169+
}
170+
157171
// New creates a new githubfs.FS object with the specified configuration.
158172
func New(opts ...Option) *FS {
159173
tenMB := 10 * 1024
160174
gfs := FS{
161-
httpClient: http.DefaultClient,
162-
githubUrl: "https://api.github.com/graphql",
163-
rawUrl: "https://raw.githubusercontent.com",
164-
threshold: tenMB,
175+
httpClient: http.DefaultClient,
176+
githubUrl: "https://api.github.com/graphql",
177+
rawUrl: "https://raw.githubusercontent.com",
178+
threshold: tenMB,
179+
getGitDirFn: getGitDir,
165180
}
166181

167182
for _, opt := range opts {
@@ -258,7 +273,7 @@ func (gfs *FS) newRepo(org, repo, branch string, releases, packages bool, size i
258273
git := r.mkdir(dirNameGit, notInPath())
259274

260275
if len(branch) > 0 {
261-
opt := withFetcher(getGitDir)
276+
opt := withFetcher(gfs.getGitDirFn)
262277
if size <= gfs.threshold {
263278
opt = withFetcher(getEntireGitDir)
264279
}
@@ -453,7 +468,7 @@ func getGitDir(gfs *FS, d *dir) error {
453468

454469
/*
455470
query {
456-
repository(name: "repo", owner: "org", followRenames: false) {
471+
repository(name: "repo", owner: "org") {
457472
object(expression: "main:") {
458473
... on Tree {
459474
entries {
@@ -477,7 +492,7 @@ func getGitDir(gfs *FS, d *dir) error {
477492
}
478493
} `graphql:"... on Tree"`
479494
} `graphql:"object(expression: $exp)"`
480-
} `graphql:"repository(name: $repo, owner: $owner, followRenames: true)"`
495+
} `graphql:"repository(name: $repo, owner: $owner)"`
481496
}
482497

483498
if err := gfs.gqlClient.Query(context.Background(), &query, vars); err != nil {

githubfs_test.go

+78-10
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,10 @@ func TestNew(t *testing.T) {
2828
{
2929
description: "basic test",
3030
}, {
31-
description: "different github url",
32-
ghUrl: "https://example.com",
33-
opts: []Option{WithGithubURL("https://example.com")},
34-
}, {
35-
description: "different http url",
36-
rawUrl: "https://example.com",
37-
opts: []Option{WithRawDownloadURL("https://example.com")},
31+
description: "use github enterprise",
32+
ghUrl: "https://example.com/api/graphql",
33+
rawUrl: "https://example.com/raw",
34+
opts: []Option{WithGithubEnterprise("https://example.com", "3.3")},
3835
}, {
3936
description: "different http client",
4037
nilHttpClient: true,
@@ -246,7 +243,24 @@ func TestMostThings(t *testing.T) {
246243
opts: []Option{WithRepo("org", "repo")},
247244
expect: []string{"./org/repo/git/main"},
248245
expectErr: true,
249-
},
246+
}, {
247+
description: "fetch enterprise v3.3 repo a file at a time.",
248+
opts: []Option{WithRepo("org", "repo"), WithThresholdInKB(0), WithGithubEnterprise("ignored", "3.3")},
249+
payload: []string{singleRepoReponse, baseDirectoryResponseV3_3, readmeResponse},
250+
expect: []string{"org/repo/git/main", "org/repo/git/main/README.md"},
251+
}, {
252+
description: "fetch enterprise v3.3 repo a file at a time with invalid json.",
253+
opts: []Option{WithRepo("org", "repo"), WithThresholdInKB(0), WithGithubEnterprise("ignored", "3.3")},
254+
payload: []string{singleRepoReponse, invalidJsonResponse},
255+
expect: []string{"org/repo/git/main/README.md"},
256+
expectErr: true,
257+
}, {
258+
description: "fetch enterprise v3.3 repo a file at a time with invalid file mode.",
259+
opts: []Option{WithRepo("org", "repo"), WithThresholdInKB(0), WithGithubEnterprise("ignored", "3.3")},
260+
payload: []string{singleRepoReponse, baseDirectoryResponseUnownFileModeV3_3},
261+
expect: []string{"org/repo/git/main/README.md"},
262+
expectErr: true,
263+
}, {},
250264
}
251265

252266
for _, tc := range tests {
@@ -292,8 +306,8 @@ func TestMostThings(t *testing.T) {
292306

293307
server.Start()
294308

295-
tc.opts = append(tc.opts, WithGithubURL(server.URL), WithRawDownloadURL(server.URL))
296-
gfs := New(tc.opts...)
309+
opts := append(tc.opts, withTestURL(server.URL))
310+
gfs := New(opts...)
297311
require.NotNil(gfs)
298312

299313
for _, path := range tc.expect {
@@ -682,6 +696,60 @@ var baseDirectoryResponseUnownFileMode = `{
682696
}
683697
}`
684698

699+
var baseDirectoryResponseV3_3 = `{
700+
"data": {
701+
"repository": {
702+
"object": {
703+
"entries": [
704+
{
705+
"name": ".github",
706+
"mode": 16384
707+
},
708+
{
709+
"name": ".gitignore",
710+
"mode": 33188
711+
},
712+
{
713+
"name": "README.md",
714+
"mode": 33188
715+
},
716+
{
717+
"name": "executable",
718+
"mode": 33261
719+
}
720+
]
721+
}
722+
}
723+
}
724+
}`
725+
726+
var baseDirectoryResponseUnownFileModeV3_3 = `{
727+
"data": {
728+
"repository": {
729+
"object": {
730+
"entries": [
731+
{
732+
"name": ".github",
733+
"mode": 16384
734+
},
735+
{
736+
"name": ".gitignore",
737+
"mode": 33188
738+
},
739+
{
740+
"name": "README.md",
741+
"mode": 33188
742+
},
743+
{
744+
"name": "unknown",
745+
"mode": 99999
746+
}
747+
]
748+
}
749+
}
750+
}
751+
}`
752+
685753
var readmeResponse = `Readme.md contents`
686754

687755
var entireRepoResponse = `{

0 commit comments

Comments
 (0)