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

Add new project and group access token events to webhook event types #1916

Merged
merged 2 commits into from Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 33 additions & 18 deletions event_parsing.go
Expand Up @@ -27,24 +27,25 @@ type EventType string

// List of available event types.
const (
EventConfidentialIssue EventType = "Confidential Issue Hook"
EventConfidentialNote EventType = "Confidential Note Hook"
EventTypeBuild EventType = "Build Hook"
EventTypeDeployment EventType = "Deployment Hook"
EventTypeFeatureFlag EventType = "Feature Flag Hook"
EventTypeIssue EventType = "Issue Hook"
EventTypeJob EventType = "Job Hook"
EventTypeMember EventType = "Member Hook"
EventTypeMergeRequest EventType = "Merge Request Hook"
EventTypeNote EventType = "Note Hook"
EventTypePipeline EventType = "Pipeline Hook"
EventTypePush EventType = "Push Hook"
EventTypeRelease EventType = "Release Hook"
EventTypeServiceHook EventType = "Service Hook"
EventTypeSubGroup EventType = "Subgroup Hook"
EventTypeSystemHook EventType = "System Hook"
EventTypeTagPush EventType = "Tag Push Hook"
EventTypeWikiPage EventType = "Wiki Page Hook"
EventConfidentialIssue EventType = "Confidential Issue Hook"
EventConfidentialNote EventType = "Confidential Note Hook"
EventTypeBuild EventType = "Build Hook"
EventTypeDeployment EventType = "Deployment Hook"
EventTypeFeatureFlag EventType = "Feature Flag Hook"
EventTypeIssue EventType = "Issue Hook"
EventTypeJob EventType = "Job Hook"
EventTypeMember EventType = "Member Hook"
EventTypeMergeRequest EventType = "Merge Request Hook"
EventTypeNote EventType = "Note Hook"
EventTypePipeline EventType = "Pipeline Hook"
EventTypePush EventType = "Push Hook"
EventTypeRelease EventType = "Release Hook"
EventTypeResourceAccessToken EventType = "Resource Access Token Hook"
EventTypeServiceHook EventType = "Service Hook"
EventTypeSubGroup EventType = "Subgroup Hook"
EventTypeSystemHook EventType = "System Hook"
EventTypeTagPush EventType = "Tag Push Hook"
EventTypeWikiPage EventType = "Wiki Page Hook"
)

const (
Expand Down Expand Up @@ -252,6 +253,20 @@ func ParseWebhook(eventType EventType, payload []byte) (event interface{}, err e
event = &PushEvent{}
case EventTypeRelease:
event = &ReleaseEvent{}
case EventTypeResourceAccessToken:
data := map[string]interface{}{}
err := json.Unmarshal(payload, &data)
if err != nil {
return nil, err
}

if _, ok := data["project"]; ok {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The payload is mostly the same for both types. Depending on the actual type, either group_id/group or project_id/project will be present. A bit of a change compared to other events… 🤷

event = &ProjectResourceAccessTokenEvent{}
} else if _, ok := data["group"]; ok {
event = &GroupResourceAccessTokenEvent{}
} else {
return nil, fmt.Errorf("unexpected resource access token payload")
}
case EventTypeServiceHook:
service := &serviceEvent{}
err := json.Unmarshal(payload, service)
Expand Down
38 changes: 37 additions & 1 deletion event_parsing_webhook_test.go
Expand Up @@ -101,7 +101,7 @@ func TestParseCommitCommentHook(t *testing.T) {
}
}

func TestParseFeatureFLagHook(t *testing.T) {
func TestParseFeatureFlagHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/feature_flag.json")

parsedEvent, err := ParseWebhook("Feature Flag Hook", raw)
Expand Down Expand Up @@ -147,6 +147,24 @@ func TestParseFeatureFLagHook(t *testing.T) {
}
}

func TestParseGroupResourceAccessTokenHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/resource_access_token_group.json")

parsedEvent, err := ParseWebhook("Resource Access Token Hook", raw)
if err != nil {
t.Errorf("Error parsing group resource access token hook: %s", err)
}

event, ok := parsedEvent.(*GroupResourceAccessTokenEvent)
if !ok {
t.Errorf("Expected GroupResourceAccessTokenEvent, but parsing produced %T", parsedEvent)
}

if event.GroupID != 35 {
t.Errorf("GroupID is %v, want %v", event.GroupID, 35)
}
}

func TestParseHookWebHook(t *testing.T) {
parsedEvent1, err := ParseHook("Merge Request Hook", loadFixture("testdata/webhooks/merge_request.json"))
if err != nil {
Expand Down Expand Up @@ -351,6 +369,24 @@ func TestParsePipelineHook(t *testing.T) {
}
}

func TestParseProjectResourceAccessTokenHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/resource_access_token_project.json")

parsedEvent, err := ParseWebhook("Resource Access Token Hook", raw)
if err != nil {
t.Errorf("Error parsing project resource access token hook: %s", err)
}

event, ok := parsedEvent.(*ProjectResourceAccessTokenEvent)
if !ok {
t.Errorf("Expected ProjectResourceAccessTokenEvent, but parsing produced %T", parsedEvent)
}

if event.ProjectID != 7 {
t.Errorf("ProjectID is %v, want %v", event.ProjectID, 7)
}
}

func TestParsePushHook(t *testing.T) {
raw := loadFixture("testdata/webhooks/push.json")

Expand Down
55 changes: 55 additions & 0 deletions event_webhook_types.go
Expand Up @@ -205,6 +205,32 @@ type FeatureFlagEvent struct {
} `json:"object_attributes"`
}

// GroupResourceAccessTokenEvent represents a resource access token event for a group
//
// GitLab API docs:
// https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#project-and-group-access-token-events
type GroupResourceAccessTokenEvent struct {
EventName string `json:"event_name"`
ObjectKind string `json:"object_kind"`
GroupID int `json:"group_id"`
Group struct {
GroupID int `json:"group_id"`
GroupName string `json:"group_name"`
GroupPath string `json:"group_path"`
FullPath string `json:"full_path"`
} `json:"group"`
ObjectAttributes *ResourceAccessToken `json:"object_attributes"`
}

// ResourceAccessToken represents an access token record.
type ResourceAccessToken struct {
ID int `json:"id"`
UserID int `json:"user_id"`
Name string `json:"name"`
CreatedAt *time.Time `json:"created_at"`
ExpiresAt *ISOTime `json:"expires_at"`
}

// IssueCommentEvent represents a comment on an issue event.
//
// GitLab API docs:
Expand Down Expand Up @@ -899,6 +925,35 @@ type PipelineEvent struct {
} `json:"builds"`
}

// ProjectResourceAccessTokenEvent represents a resource access token event for a project
//
// GitLab API docs:
// https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#project-and-group-access-token-events
type ProjectResourceAccessTokenEvent struct {
EventName string `json:"event_name"`
ObjectKind string `json:"object_kind"`
ProjectID int `json:"project_id"`
Project struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
WebURL string `json:"web_url"`
AvatarURL string `json:"avatar_url"`
GitSSHURL string `json:"git_ssh_url"`
GitHTTPURL string `json:"git_http_url"`
Namespace string `json:"namespace"`
VisibilityLevel int `json:"visibility_level"`
PathWithNamespace string `json:"path_with_namespace"`
DefaultBranch string `json:"default_branch"`
CIConfigPath string `json:"ci_config_path"`
Homepage string `json:"homepage"`
URL string `json:"url"`
SSHURL string `json:"ssh_url"`
HTTPURL string `json:"http_url"`
} `json:"project"`
ObjectAttributes ResourceAccessToken `json:"object_attributes"`
}

// PushEvent represents a push event.
//
// GitLab API docs:
Expand Down
98 changes: 98 additions & 0 deletions event_webhook_types_test.go
Expand Up @@ -286,6 +286,49 @@ func TestFeatureFlagEventUnmarshal(t *testing.T) {
}
}

func TestGroupResourceAccessTokenEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/resource_access_token_group.json")
var event *GroupResourceAccessTokenEvent
err := json.Unmarshal(jsonObject, &event)
if err != nil {
t.Errorf("could not unmarshal event: %v\n ", err.Error())
}

if event == nil {
t.Errorf("event is null")
}

createdAt, err := time.Parse(time.RFC3339, "2024-02-05T03:13:44.855Z")
if err != nil {
t.Fatalf("could not parse time: %v", err)
}

expiresAt, err := ParseISOTime("2024-01-26")
if err != nil {
t.Fatalf("could not parse ISO time: %v", err)
}

expected := &GroupResourceAccessTokenEvent{
GroupID: 35,
ObjectKind: "access_token",
EventName: "expiring_access_token",
ObjectAttributes: &ResourceAccessToken{
ID: 25,
UserID: 90,
Name: "acd",
CreatedAt: &createdAt,
ExpiresAt: &expiresAt,
},
}

expected.Group.GroupID = 35
expected.Group.GroupName = "Twitter"
expected.Group.GroupPath = "twitter"
expected.Group.FullPath = "twitter"

assert.Equal(t, expected, event)
}

func TestIssueCommentEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/note_issue.json")

Expand Down Expand Up @@ -1014,6 +1057,61 @@ func TestPipelineEventUnmarshal(t *testing.T) {
}
}

func TestProjectResourceAccessTokenEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/resource_access_token_project.json")
var event *ProjectResourceAccessTokenEvent
err := json.Unmarshal(jsonObject, &event)
if err != nil {
t.Errorf("could not unmarshal event: %v\n ", err.Error())
}

if event == nil {
t.Errorf("event is null")
}

createdAt, err := time.Parse(time.RFC3339, "2024-02-05T03:13:44.855Z")
if err != nil {
t.Fatalf("could not parse time: %v", err)
}

expiresAt, err := ParseISOTime("2024-01-26")
if err != nil {
t.Fatalf("could not parse ISO time: %v", err)
}

expected := &ProjectResourceAccessTokenEvent{
ProjectID: 7,
ObjectKind: "access_token",
EventName: "expiring_access_token",
ObjectAttributes: ResourceAccessToken{
ID: 25,
UserID: 90,
Name: "acd",
CreatedAt: &createdAt,
ExpiresAt: &expiresAt,
},
}

expected.Project.ID = 7
expected.Project.Name = "Flight"
expected.Project.Description = "Eum dolore maxime atque reprehenderit voluptatem."
expected.Project.WebURL = "https://example.com/flightjs/Flight"
expected.Project.AvatarURL = ""
expected.Project.GitSSHURL = "ssh://git@example.com/flightjs/Flight.git"
expected.Project.GitHTTPURL = "https://example.com/flightjs/Flight.git"
expected.Project.Namespace = "Flightjs"
expected.Project.VisibilityLevel = 0
expected.Project.PathWithNamespace = "flightjs/Flight"
expected.Project.DefaultBranch = "master"
expected.Project.CIConfigPath = ""
expected.Project.Homepage = "https://example.com/flightjs/Flight"
expected.Project.URL = "ssh://git@example.com/flightjs/Flight.git"
expected.Project.SSHURL = "ssh://git@example.com/flightjs/Flight.git"
expected.Project.HTTPURL = "https://example.com/flightjs/Flight.git"

assert.Equal(t, expected, event)
}

func TestPushEventUnmarshal(t *testing.T) {
jsonObject := loadFixture("testdata/webhooks/push.json")
var event *PushEvent
Expand Down
18 changes: 18 additions & 0 deletions testdata/webhooks/resource_access_token_group.json
@@ -0,0 +1,18 @@
{
"object_kind": "access_token",
"group_id": 35,
"group": {
"group_name": "Twitter",
"group_path": "twitter",
"full_path": "twitter",
"group_id": 35
},
"object_attributes": {
"user_id": 90,
"created_at": "2024-02-05T03:13:44.855Z",
"id": 25,
"name": "acd",
"expires_at": "2024-01-26"
},
"event_name": "expiring_access_token"
}
30 changes: 30 additions & 0 deletions testdata/webhooks/resource_access_token_project.json
@@ -0,0 +1,30 @@
{
"object_kind": "access_token",
"project_id": 7,
"project": {
"id": 7,
"name": "Flight",
"description": "Eum dolore maxime atque reprehenderit voluptatem.",
"web_url": "https://example.com/flightjs/Flight",
"avatar_url": null,
"git_ssh_url": "ssh://git@example.com/flightjs/Flight.git",
"git_http_url": "https://example.com/flightjs/Flight.git",
"namespace": "Flightjs",
"visibility_level": 0,
"path_with_namespace": "flightjs/Flight",
"default_branch": "master",
"ci_config_path": null,
"homepage": "https://example.com/flightjs/Flight",
"url": "ssh://git@example.com/flightjs/Flight.git",
"ssh_url": "ssh://git@example.com/flightjs/Flight.git",
"http_url": "https://example.com/flightjs/Flight.git"
},
"object_attributes": {
"user_id": 90,
"created_at": "2024-02-05T03:13:44.855Z",
"id": 25,
"name": "acd",
"expires_at": "2024-01-26"
},
"event_name": "expiring_access_token"
}