From 4dc8ed891c5461078d7726a28ac7979fef314d04 Mon Sep 17 00:00:00 2001 From: kbilchenko Date: Thu, 4 Nov 2021 14:16:40 +0100 Subject: [PATCH 1/4] Add handling for github enterprise Signed-off-by: kbilchenko --- github.go | 21 ++++++++----- github_graphql.go | 76 +++++++++++++++++++++++++++++++++++++++++++++-- github_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++---- model.go | 12 ++++++++ 4 files changed, 170 insertions(+), 15 deletions(-) diff --git a/github.go b/github.go index 399ae2d..e91a36b 100644 --- a/github.go +++ b/github.go @@ -37,8 +37,9 @@ type GitHub interface { } type GitHubClient struct { - client *github.Client - clientV4 *githubv4.Client + client *github.Client + clientV4 *githubv4.Client + isEnterprise bool owner string repository string @@ -68,6 +69,7 @@ func NewGitHubClient(source Source) (*GitHubClient, error) { client := github.NewClient(httpClient) clientV4 := githubv4.NewClient(httpClient) + var isEnterprise bool if source.GitHubAPIURL != "" { var err error @@ -91,6 +93,7 @@ func NewGitHubClient(source Source) (*GitHubClient, error) { v4URL = source.GitHubAPIURL + "graphql" } clientV4 = githubv4.NewEnterpriseClient(v4URL, httpClient) + isEnterprise = true } if source.GitHubV4APIURL != "" { @@ -111,16 +114,20 @@ func NewGitHubClient(source Source) (*GitHubClient, error) { } return &GitHubClient{ - client: client, - clientV4: clientV4, - owner: owner, - repository: source.Repository, - accessToken: source.AccessToken, + client: client, + clientV4: clientV4, + isEnterprise: isEnterprise, + owner: owner, + repository: source.Repository, + accessToken: source.AccessToken, }, nil } func (g *GitHubClient) ListReleases() ([]*github.RepositoryRelease, error) { if g.accessToken != "" { + if g.isEnterprise { + return g.listReleasesV4EnterPrice() + } return g.listReleasesV4() } opt := &github.ListOptions{PerPage: 100} diff --git a/github_graphql.go b/github_graphql.go index 5d80b7a..a8e0063 100644 --- a/github_graphql.go +++ b/github_graphql.go @@ -4,14 +4,84 @@ import ( "context" "encoding/base64" "errors" + "github.com/google/go-github/v39/github" + "github.com/shurcooL/githubv4" "regexp" "strconv" "time" - - "github.com/google/go-github/v39/github" - "github.com/shurcooL/githubv4" ) +func (g *GitHubClient) listReleasesV4EnterPrice() ([]*github.RepositoryRelease, error) { + if g.clientV4 == nil { + return nil, errors.New("github graphql is not been initialised") + } + var listReleasesEnterprise struct { + Repository struct { + Releases struct { + Edges []struct { + Node struct { + ReleaseObjectEnterprise + } + } `graphql:"edges"` + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } `graphql:"pageInfo"` + } `graphql:"releases(first:$releasesCount, after: $releaseCursor, orderBy: {field: CREATED_AT, direction: DESC})"` + } `graphql:"repository(owner:$repositoryOwner,name:$repositoryName)"` + } + + vars := map[string]interface{}{ + "repositoryOwner": githubv4.String(g.owner), + "repositoryName": githubv4.String(g.repository), + "releaseCursor": (*githubv4.String)(nil), + "releasesCount": githubv4.Int(100), + } + + var allReleases []*github.RepositoryRelease + for { + if err := g.clientV4.Query(context.TODO(), &listReleasesEnterprise, vars); err != nil { + return nil, err + } + for _, r := range listReleasesEnterprise.Repository.Releases.Edges { + r := r + publishedAt, _ := time.ParseInLocation(time.RFC3339, r.Node.PublishedAt.Time.Format(time.RFC3339), time.UTC) + createdAt, _ := time.ParseInLocation(time.RFC3339, r.Node.CreatedAt.Time.Format(time.RFC3339), time.UTC) + var releaseID int64 + decodedID, err := base64.StdEncoding.DecodeString(r.Node.ID) + if err != nil { + return nil, err + } + re := regexp.MustCompile(`.*[^\d]`) + decodedID = re.ReplaceAll(decodedID, []byte("")) + if string(decodedID) == "" { + return nil, errors.New("bad release id from graph ql api") + } + releaseID, err = strconv.ParseInt(string(decodedID), 10, 64) + if err != nil { + return nil, err + } + + allReleases = append(allReleases, &github.RepositoryRelease{ + ID: &releaseID, + TagName: &r.Node.TagName, + Name: &r.Node.Name, + Prerelease: &r.Node.IsPrerelease, + Draft: &r.Node.IsDraft, + URL: &r.Node.URL, + PublishedAt: &github.Timestamp{Time: publishedAt}, + CreatedAt: &github.Timestamp{Time: createdAt}, + }) + } + if !listReleasesEnterprise.Repository.Releases.PageInfo.HasNextPage { + break + } + vars["releaseCursor"] = listReleasesEnterprise.Repository.Releases.PageInfo.EndCursor + + } + return allReleases, nil +} + func (g *GitHubClient) listReleasesV4() ([]*github.RepositoryRelease, error) { if g.clientV4 == nil { return nil, errors.New("github graphql is not been initialised") diff --git a/github_test.go b/github_test.go index aac2c68..5e5bf51 100644 --- a/github_test.go +++ b/github_test.go @@ -57,6 +57,45 @@ const ( } }` + multiPageRespEnterprise = `{ + "data": { + "repository": { + "releases": { + "edges": [ + { + "node": { + "createdAt": "2010-10-01T00:58:07Z", + "id": "MDc6UmVsZWFzZTMyMDk1MTAz", + "name": "xyz", + "publishedAt": "2010-10-02T15:39:53Z", + "tagName": "xyz", + "url": "https://github.com/xyz/xyz/releases/tag/xyz", + "isDraft": false, + "isPrerelease": false + } + }, + { + "node": { + "createdAt": "2010-08-27T13:55:36Z", + "id": "MDc6UmVsZWFzZTMwMjMwNjU5", + "name": "xyz", + "publishedAt": "2010-08-27T17:18:06Z", + "tagName": "xyz", + "url": "https://github.com/xyz/xyz/releases/tag/xyz", + "isDraft": false, + "isPrerelease": false + } + } + ], + "pageInfo": { + "endCursor": "Y3Vyc29yOnYyOpK5MjAyMC0xMC0wMVQwMjo1ODowNyswMjowMM4B6bt_", + "hasNextPage": true + } + } + } + } +}` + singlePageResp = `{ "data": { "repository": { @@ -84,6 +123,33 @@ const ( } } }` + + singlePageRespEnterprise = `{ + "data": { + "repository": { + "releases": { + "edges": [ + { + "node": { + "createdAt": "2010-10-10T01:01:07Z", + "id": "MDc6UmVsZWFzZTMzMjIyMjQz", + "name": "xyq", + "publishedAt": "2010-10-10T15:39:53Z", + "tagName": "xyq", + "url": "https://github.com/xyq/xyq/releases/tag/xyq", + "isDraft": false, + "isPrerelease": false + } + } + ], + "pageInfo": { + "endCursor": "Y3Vyc29yOnYyOpK5MjAyMC0xMC0wMVQwMjo1ODowNyswMjowMM4B6bt_", + "hasNextPage": false + } + } + } + } +}` invalidPageIdResp = `{ "data": { "repository": { @@ -171,7 +237,7 @@ var _ = Describe("GitHub Client", func() { server.AppendHandlers( ghttp.CombineHandlers( ghttp.VerifyRequest("POST", "/api/graphql"), - ghttp.RespondWith(200, singlePageResp), + ghttp.RespondWith(200, singlePageRespEnterprise), ), ) @@ -188,7 +254,7 @@ var _ = Describe("GitHub Client", func() { server.AppendHandlers( ghttp.CombineHandlers( ghttp.VerifyRequest("POST", "/api/graphql"), - ghttp.RespondWith(200, singlePageResp), + ghttp.RespondWith(200, singlePageRespEnterprise), ), ) @@ -216,7 +282,7 @@ var _ = Describe("GitHub Client", func() { server.AppendHandlers( ghttp.CombineHandlers( ghttp.VerifyRequest("POST", "/graphql"), - ghttp.RespondWith(200, singlePageResp), + ghttp.RespondWith(200, singlePageRespEnterprise), ghttp.VerifyHeaderKV("Authorization", "Bearer abc123"), ), ) @@ -263,11 +329,11 @@ var _ = Describe("GitHub Client", func() { server.AppendHandlers( ghttp.CombineHandlers( ghttp.VerifyRequest("POST", "/graphql"), - ghttp.RespondWith(200, multiPageResp), + ghttp.RespondWith(200, multiPageRespEnterprise), ), ghttp.CombineHandlers( ghttp.VerifyRequest("POST", "/graphql"), - ghttp.RespondWith(200, singlePageResp), + ghttp.RespondWith(200, singlePageRespEnterprise), ), ) }) diff --git a/model.go b/model.go index 0ed351f..215b8b2 100644 --- a/model.go +++ b/model.go @@ -15,3 +15,15 @@ type ReleaseObject struct { TagName string `graphql:"tagName"` URL string `graphql:"url"` } + +// ReleaseObjectEnterprise Workaround until DatabaseId will appear in enterprise installation +type ReleaseObjectEnterprise struct { + CreatedAt githubv4.DateTime `graphql:"createdAt"` + PublishedAt githubv4.DateTime `graphql:"publishedAt"` + ID string `graphql:"id"` + IsDraft bool `graphql:"isDraft"` + IsPrerelease bool `graphql:"isPrerelease"` + Name string `graphql:"name"` + TagName string `graphql:"tagName"` + URL string `graphql:"url"` +} From 57ce9f327b23c26aaeaeb62aa56e449f387b9a8c Mon Sep 17 00:00:00 2001 From: kbilchenko Date: Thu, 4 Nov 2021 14:16:40 +0100 Subject: [PATCH 2/4] Add handling for github enterprise Signed-off-by: kbilchenko --- github_test.go | 69 -------------------------------------------------- 1 file changed, 69 deletions(-) diff --git a/github_test.go b/github_test.go index 5e5bf51..0bf342d 100644 --- a/github_test.go +++ b/github_test.go @@ -16,47 +16,6 @@ import ( ) const ( - multiPageResp = `{ - "data": { - "repository": { - "releases": { - "edges": [ - { - "node": { - "createdAt": "2010-10-01T00:58:07Z", - "id": "MDc6UmVsZWFzZTMyMDk1MTAz", - "databaseId": 32095103, - "name": "xyz", - "publishedAt": "2010-10-02T15:39:53Z", - "tagName": "xyz", - "url": "https://github.com/xyz/xyz/releases/tag/xyz", - "isDraft": false, - "isPrerelease": false - } - }, - { - "node": { - "createdAt": "2010-08-27T13:55:36Z", - "id": "MDc6UmVsZWFzZTMwMjMwNjU5", - "databaseId": 30230659, - "name": "xyz", - "publishedAt": "2010-08-27T17:18:06Z", - "tagName": "xyz", - "url": "https://github.com/xyz/xyz/releases/tag/xyz", - "isDraft": false, - "isPrerelease": false - } - } - ], - "pageInfo": { - "endCursor": "Y3Vyc29yOnYyOpK5MjAyMC0xMC0wMVQwMjo1ODowNyswMjowMM4B6bt_", - "hasNextPage": true - } - } - } - } -}` - multiPageRespEnterprise = `{ "data": { "repository": { @@ -96,34 +55,6 @@ const ( } }` - singlePageResp = `{ - "data": { - "repository": { - "releases": { - "edges": [ - { - "node": { - "createdAt": "2010-10-10T01:01:07Z", - "id": "MDc6UmVsZWFzZTMzMjIyMjQz", - "databaseId": 33222243, - "name": "xyq", - "publishedAt": "2010-10-10T15:39:53Z", - "tagName": "xyq", - "url": "https://github.com/xyq/xyq/releases/tag/xyq", - "isDraft": false, - "isPrerelease": false - } - } - ], - "pageInfo": { - "endCursor": "Y3Vyc29yOnYyOpK5MjAyMC0xMC0wMVQwMjo1ODowNyswMjowMM4B6bt_", - "hasNextPage": false - } - } - } - } -}` - singlePageRespEnterprise = `{ "data": { "repository": { From 51ba4a45c14fc1723268ed1f647cc4f735e8bd70 Mon Sep 17 00:00:00 2001 From: kbilchenko Date: Tue, 9 Nov 2021 09:15:53 +0100 Subject: [PATCH 3/4] Add missed initialisation for isEnterprise in case of GitHubV4APIURL is set to custom Signed-off-by: kbilchenko --- github.go | 1 + 1 file changed, 1 insertion(+) diff --git a/github.go b/github.go index e91a36b..84e92ee 100644 --- a/github.go +++ b/github.go @@ -98,6 +98,7 @@ func NewGitHubClient(source Source) (*GitHubClient, error) { if source.GitHubV4APIURL != "" { clientV4 = githubv4.NewEnterpriseClient(source.GitHubV4APIURL, httpClient) + isEnterprise = true } if source.GitHubUploadsURL != "" { From 9128ad0cc954c984175318ed47454ab35bd98cfd Mon Sep 17 00:00:00 2001 From: Kirill Bilchenko Date: Fri, 12 Nov 2021 21:42:21 +0100 Subject: [PATCH 4/4] Update model.go Co-authored-by: Rui Yang Signed-off-by: kbilchenko --- model.go | 1 + 1 file changed, 1 insertion(+) diff --git a/model.go b/model.go index 215b8b2..216f0b2 100644 --- a/model.go +++ b/model.go @@ -17,6 +17,7 @@ type ReleaseObject struct { } // ReleaseObjectEnterprise Workaround until DatabaseId will appear in enterprise installation +// https://github.com/concourse/github-release-resource/issues/109 type ReleaseObjectEnterprise struct { CreatedAt githubv4.DateTime `graphql:"createdAt"` PublishedAt githubv4.DateTime `graphql:"publishedAt"`