Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance(webhook): handle repository transfer events #883

Merged
merged 5 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 32 additions & 29 deletions api/webhook/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ func handleRepositoryEvent(c *gin.Context, m *types.Metadata, h *library.Hook, r

switch h.GetEventAction() {
// if action is rename, go through rename routine
case constants.ActionRenamed:
case constants.ActionRenamed, constants.ActionTransferred:
r, err := renameRepository(h, r, c, m)
if err != nil {
h.SetStatus(constants.StatusFailure)
Expand Down Expand Up @@ -779,34 +779,16 @@ func handleRepositoryEvent(c *gin.Context, m *types.Metadata, h *library.Hook, r
// renameRepository is a helper function that takes the old name of the repo,
// queries the database for the repo that matches that name and org, and updates
// that repo to its new name in order to preserve it. It also updates the secrets
// associated with that repo.
// associated with that repo as well as build links for the UI.
func renameRepository(h *library.Hook, r *library.Repo, c *gin.Context, m *types.Metadata) (*library.Repo, error) {
logrus.Debugf("renaming repository from %s to %s", r.GetPreviousName(), r.GetName())
logrus.Infof("renaming repository from %s to %s", r.GetPreviousName(), r.GetName())
// get the old name of the repo
previousName := r.GetPreviousName()
// get the repo from the database that matches the old name
dbR, err := database.FromContext(c).GetRepoForOrg(r.GetOrg(), previousName)
if err != nil {
retErr := fmt.Errorf("%s: failed to get repo %s/%s from database", baseErr, r.GetOrg(), previousName)
util.HandleError(c, http.StatusBadRequest, retErr)

h.SetStatus(constants.StatusFailure)
h.SetError(retErr.Error())

return nil, retErr
}
prevOrg, prevRepo := util.SplitFullName(r.GetPreviousName())

// update the repo name information
dbR.SetName(r.GetName())
dbR.SetFullName(r.GetFullName())
dbR.SetClone(r.GetClone())
dbR.SetLink(r.GetLink())
dbR.SetPreviousName(previousName)

// update the repo in the database
err = database.FromContext(c).UpdateRepo(dbR)
// get the repo from the database that matches the old name
dbR, err := database.FromContext(c).GetRepoForOrg(prevOrg, prevRepo)
if err != nil {
retErr := fmt.Errorf("%s: failed to update repo %s/%s in database", baseErr, r.GetOrg(), previousName)
retErr := fmt.Errorf("%s: failed to get repo %s/%s from database", baseErr, prevOrg, prevRepo)
util.HandleError(c, http.StatusBadRequest, retErr)

h.SetStatus(constants.StatusFailure)
Expand Down Expand Up @@ -840,7 +822,7 @@ func renameRepository(h *library.Hook, r *library.Repo, c *gin.Context, m *types
// get total number of secrets associated with repository
t, err := database.FromContext(c).CountSecretsForRepo(dbR, map[string]interface{}{})
if err != nil {
return nil, fmt.Errorf("unable to get secret count for repo %s/%s: %w", r.GetOrg(), previousName, err)
return nil, fmt.Errorf("unable to get secret count for repo %s/%s: %w", prevOrg, prevRepo, err)
}

secrets := []*library.Secret{}
Expand All @@ -849,7 +831,7 @@ func renameRepository(h *library.Hook, r *library.Repo, c *gin.Context, m *types
for repoSecrets := int64(0); repoSecrets < t; repoSecrets += 100 {
s, _, err := database.FromContext(c).ListSecretsForRepo(dbR, map[string]interface{}{}, page, 100)
if err != nil {
return nil, fmt.Errorf("unable to get secret list for repo %s/%s: %w", r.GetOrg(), previousName, err)
return nil, fmt.Errorf("unable to get secret list for repo %s/%s: %w", prevOrg, prevRepo, err)
}

secrets = append(secrets, s...)
Expand All @@ -859,11 +841,12 @@ func renameRepository(h *library.Hook, r *library.Repo, c *gin.Context, m *types

// update secrets to point to the new repository name
for _, secret := range secrets {
secret.SetOrg(r.GetOrg())
secret.SetRepo(r.GetName())

err = database.FromContext(c).UpdateSecret(secret)
if err != nil {
return nil, fmt.Errorf("unable to update secret for repo %s/%s: %w", r.GetOrg(), previousName, err)
return nil, fmt.Errorf("unable to update secret for repo %s/%s: %w", prevOrg, prevRepo, err)
}
}

Expand All @@ -890,7 +873,7 @@ func renameRepository(h *library.Hook, r *library.Repo, c *gin.Context, m *types
// update build link to route to proper repo name
for _, build := range builds {
build.SetLink(
fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, dbR.GetFullName(), build.GetNumber()),
fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), build.GetNumber()),
)

_, err = database.FromContext(c).UpdateBuild(build)
Expand All @@ -899,5 +882,25 @@ func renameRepository(h *library.Hook, r *library.Repo, c *gin.Context, m *types
}
}

// update the repo name information
dbR.SetName(r.GetName())
dbR.SetOrg(r.GetOrg())
dbR.SetFullName(r.GetFullName())
dbR.SetClone(r.GetClone())
dbR.SetLink(r.GetLink())
dbR.SetPreviousName(r.GetPreviousName())

// update the repo in the database
err = database.FromContext(c).UpdateRepo(dbR)
if err != nil {
retErr := fmt.Errorf("%s: failed to update repo %s/%s in database", baseErr, prevOrg, prevRepo)
util.HandleError(c, http.StatusBadRequest, retErr)

h.SetStatus(constants.StatusFailure)
h.SetError(retErr.Error())

return nil, retErr
}

return dbR, nil
}
9 changes: 1 addition & 8 deletions router/middleware/perm/perm.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,7 @@ func MustSecretAdmin() gin.HandlerFunc {

// if caller is worker with build token, verify it has access to requested secret
if strings.EqualFold(cl.TokenType, constants.WorkerBuildTokenType) {
// split repo full name into org and repo
repoSlice := strings.Split(cl.Repo, "/")
if len(repoSlice) != 2 {
logger.Errorf("unable to parse repo claim in build token")
}

org := repoSlice[0]
repo := repoSlice[1]
org, repo := util.SplitFullName(cl.Repo)

switch t {
case constants.SecretShared:
Expand Down
159 changes: 159 additions & 0 deletions scm/github/testdata/hooks/repository_transferred.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
{
"action": "transferred",
"changes": {
"owner": {
"from": {
"user": {
"login": "Old-Codertocat",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these supposed to be octocat?

"id": 4,
"node_id": "MDQ6VXNlcjQ=",
"avatar_url": "https://octocoders.github.io/avatars/u/4?",
"gravatar_id": "",
"url": "https://octocoders.github.io/api/v3/users/Codertocat",
"html_url": "https://octocoders.github.io/Codertocat",
"followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
"following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
"gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
"starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
"subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
"organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
"repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
"events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
"received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
"type": "User",
"site_admin": false
}
}
}
},
"repository": {
"id": 118,
"node_id": "MDEwOlJlcG9zaXRvcnkxMTg=",
"name": "Hello-World",
"full_name": "Codertocat/Hello-World",
"private": false,
"owner": {
"login": "Codertocat",
"id": 4,
"node_id": "MDQ6VXNlcjQ=",
"avatar_url": "https://octocoders.github.io/avatars/u/4?",
"gravatar_id": "",
"url": "https://octocoders.github.io/api/v3/users/Codertocat",
"html_url": "https://octocoders.github.io/Codertocat",
"followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
"following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
"gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
"starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
"subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
"organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
"repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
"events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
"received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://octocoders.github.io/Codertocat/Hello-World",
"description": null,
"fork": false,
"url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World",
"forks_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/forks",
"keys_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/keys{/key_id}",
"collaborators_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/collaborators{/collaborator}",
"teams_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/teams",
"hooks_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/hooks",
"issue_events_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues/events{/number}",
"events_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/events",
"assignees_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/assignees{/user}",
"branches_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/branches{/branch}",
"tags_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/tags",
"blobs_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/blobs{/sha}",
"git_tags_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/tags{/sha}",
"git_refs_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/refs{/sha}",
"trees_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/trees{/sha}",
"statuses_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/statuses/{sha}",
"languages_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/languages",
"stargazers_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/stargazers",
"contributors_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/contributors",
"subscribers_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/subscribers",
"subscription_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/subscription",
"commits_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/commits{/sha}",
"git_commits_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/commits{/sha}",
"comments_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/comments{/number}",
"issue_comment_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues/comments{/number}",
"contents_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/contents/{+path}",
"compare_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/compare/{base}...{head}",
"merges_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/merges",
"archive_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/{archive_format}{/ref}",
"downloads_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/downloads",
"issues_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues{/number}",
"pulls_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/pulls{/number}",
"milestones_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/milestones{/number}",
"notifications_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/notifications{?since,all,participating}",
"labels_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/labels{/name}",
"releases_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/releases{/id}",
"deployments_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/deployments",
"created_at": "2019-05-15T19:37:07Z",
"updated_at": "2019-05-15T19:38:25Z",
"pushed_at": "2019-05-15T19:38:23Z",
"git_url": "git://octocoders.github.io/Codertocat/Hello-World.git",
"ssh_url": "[email protected]:Codertocat/Hello-World.git",
"clone_url": "https://octocoders.github.io/Codertocat/Hello-World.git",
"svn_url": "https://octocoders.github.io/Codertocat/Hello-World",
"homepage": null,
"size": 0,
"stargazers_count": 0,
"watchers_count": 0,
"language": "Ruby",
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": true,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 2,
"license": null,
"forks": 0,
"open_issues": 2,
"watchers": 0,
"default_branch": "master"
},
"enterprise": {
"id": 1,
"slug": "github",
"name": "GitHub",
"node_id": "MDg6QnVzaW5lc3Mx",
"avatar_url": "https://octocoders.github.io/avatars/b/1?",
"description": null,
"website_url": null,
"html_url": "https://octocoders.github.io/businesses/github",
"created_at": "2019-05-14T19:31:12Z",
"updated_at": "2019-05-14T19:31:12Z"
},
"sender": {
"login": "Codertocat",
"id": 4,
"node_id": "MDQ6VXNlcjQ=",
"avatar_url": "https://octocoders.github.io/avatars/u/4?",
"gravatar_id": "",
"url": "https://octocoders.github.io/api/v3/users/Codertocat",
"html_url": "https://octocoders.github.io/Codertocat",
"followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
"following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
"gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
"starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
"subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
"organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
"repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
"events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
"received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
"type": "User",
"site_admin": false
},
"installation": {
"id": 5,
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNQ=="
}
}
15 changes: 13 additions & 2 deletions scm/github/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,19 @@ func (c *client) processRepositoryEvent(h *library.Hook, payload *github.Reposit
r.SetTopics(repo.Topics)

// if action is renamed, then get the previous name from payload
if payload.GetAction() == "renamed" {
r.SetPreviousName(payload.GetChanges().GetRepo().GetName().GetFrom())
if payload.GetAction() == constants.ActionRenamed {
r.SetPreviousName(repo.GetOwner().GetLogin() + "/" + payload.GetChanges().GetRepo().GetName().GetFrom())
}

// if action is transferred, then get the previous owner from payload
// could be a user or an org, but both are User structs
if payload.GetAction() == constants.ActionTransferred {
org := payload.GetChanges().GetOwner().GetOwnerInfo().GetOrg()
if org == nil {
org = payload.GetChanges().GetOwner().GetOwnerInfo().GetUser()
}

r.SetPreviousName(org.GetLogin() + "/" + repo.GetName())
}

h.SetEvent(constants.EventRepository)
Expand Down
67 changes: 66 additions & 1 deletion scm/github/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,72 @@ func TestGitHub_ProcessWebhook_RepositoryRename(t *testing.T) {
wantRepo.SetClone("https://octocoders.github.io/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
wantRepo.SetPreviousName("Hello-Old-World")
wantRepo.SetPreviousName("Codertocat/Hello-Old-World")
wantRepo.SetTopics(nil)

want := &types.Webhook{
Comment: "",
Hook: wantHook,
Repo: wantRepo,
}

got, err := client.ProcessWebhook(request)

if err != nil {
t.Errorf("ProcessWebhook returned err: %v", err)
}

if !reflect.DeepEqual(got, want) {
t.Errorf("ProcessWebhook is %v, want %v", got, want)
}
}

func TestGitHub_ProcessWebhook_RepositoryTransfer(t *testing.T) {
// setup router
s := httptest.NewServer(http.NotFoundHandler())
defer s.Close()

// setup request
body, err := os.Open("testdata/hooks/repository_transferred.json")
if err != nil {
t.Errorf("unable to open file: %v", err)
}

defer body.Close()

request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Event", "repository")

// setup client
client, _ := NewTest(s.URL)

// run test
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent(constants.EventRepository)
wantHook.SetEventAction(constants.ActionTransferred)
wantHook.SetBranch("master")
wantHook.SetStatus(constants.StatusSuccess)
wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks")

wantRepo := new(library.Repo)
wantRepo.SetActive(true)
wantRepo.SetOrg("Codertocat")
wantRepo.SetName("Hello-World")
wantRepo.SetFullName("Codertocat/Hello-World")
wantRepo.SetLink("https://octocoders.github.io/Codertocat/Hello-World")
wantRepo.SetClone("https://octocoders.github.io/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
wantRepo.SetPreviousName("Old-Codertocat/Hello-World")
wantRepo.SetTopics(nil)

want := &types.Webhook{
Expand Down
Loading