Skip to content

Commit df8a7f2

Browse files
qwerty287pat-s
authored andcommitted
Add release event trigger (woodpecker-ci#3226)
Supersedes woodpecker-ci#764 Bitbucket does not support release webhooks. --------- Co-authored-by: Patrick Schratz <patrick.schratz@gmail.com>
1 parent cfb6014 commit df8a7f2

File tree

35 files changed

+628
-18
lines changed

35 files changed

+628
-18
lines changed

cli/secret/secret_add.go

+1
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,6 @@ func secretCreate(c *cli.Context) error {
102102
var defaultSecretEvents = []string{
103103
woodpecker.EventPush,
104104
woodpecker.EventTag,
105+
woodpecker.EventRelease,
105106
woodpecker.EventDeploy,
106107
}

cmd/server/docs/docs.go

+5
Original file line numberDiff line numberDiff line change
@@ -3937,6 +3937,9 @@ const docTemplate = `{
39373937
"id": {
39383938
"type": "integer"
39393939
},
3940+
"is_prerelease": {
3941+
"type": "boolean"
3942+
},
39403943
"message": {
39413944
"type": "string"
39423945
},
@@ -4383,6 +4386,7 @@ const docTemplate = `{
43834386
"pull_request",
43844387
"pull_request_closed",
43854388
"tag",
4389+
"release",
43864390
"deployment",
43874391
"cron",
43884392
"manual"
@@ -4392,6 +4396,7 @@ const docTemplate = `{
43924396
"EventPull",
43934397
"EventPullClosed",
43944398
"EventTag",
4399+
"EventRelease",
43954400
"EventDeploy",
43964401
"EventCron",
43974402
"EventManual"

docs/docs/20-usage/15-terminiology/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- `pull_request`: A pull request event is triggered when a pull request is opened or a new commit is pushed to it.
3939
- `pull_request_closed`: A pull request closed event is triggered when a pull request is closed or merged.
4040
- `tag`: A tag event is triggered when a tag is pushed.
41+
- `release`: A release event is triggered when a release is created.
4142
- `manual`: A manual event is triggered when a user manually triggers a pipeline.
4243
- `cron`: A cron event is triggered when a cron job is executed.
4344

docs/docs/20-usage/20-workflow-syntax.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ when:
269269

270270
#### `event`
271271

272-
Available events: `push`, `pull_request`, `pull_request_closed`, `tag`, `deployment`, `cron`, `manual`
272+
Available events: `push`, `pull_request`, `pull_request_closed`, `tag`, `release`, `deployment`, `cron`, `manual`
273273

274274
Execute a step if the build event is a `tag`:
275275

docs/docs/20-usage/50-environment.md

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ This is the reference list of all environment variables available to your pipeli
7777
| `CI_COMMIT_AUTHOR` | commit author username |
7878
| `CI_COMMIT_AUTHOR_EMAIL` | commit author email address |
7979
| `CI_COMMIT_AUTHOR_AVATAR` | commit author avatar |
80+
| `CI_COMMIT_PRERELEASE` | release is a pre-release (empty if event is not `release`) |
8081
| | **Current pipeline** |
8182
| `CI_PIPELINE_NUMBER` | pipeline number |
8283
| `CI_PIPELINE_PARENT` | number of parent pipeline |

docs/docs/30-administration/11-forges/10-overview.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
88
| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
99
| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
10+
| Event: Release | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
1011
| Event: Deploy | :white_check_mark: | :x: | :x: | :x: |
1112
| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
1213
| [when.path filter](../../20-usage/20-workflow-syntax.md#path) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |

pipeline/frontend/metadata/const.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const (
2020
EventPull = "pull_request"
2121
EventPullClosed = "pull_request_closed"
2222
EventTag = "tag"
23+
EventRelease = "release"
2324
EventDeploy = "deployment"
2425
EventCron = "cron"
2526
EventManual = "manual"

pipeline/frontend/metadata/environment.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,12 @@ func (m *Metadata) Environ() map[string]string {
125125
// TODO Deprecated, remove in 3.x
126126
"CI_COMMIT_URL": m.Curr.ForgeURL,
127127
}
128-
if m.Curr.Event == EventTag || strings.HasPrefix(m.Curr.Commit.Ref, "refs/tags/") {
128+
if m.Curr.Event == EventTag || m.Curr.Event == EventRelease || strings.HasPrefix(m.Curr.Commit.Ref, "refs/tags/") {
129129
params["CI_COMMIT_TAG"] = strings.TrimPrefix(m.Curr.Commit.Ref, "refs/tags/")
130130
}
131+
if m.Curr.Event == EventRelease {
132+
params["CI_COMMIT_PRERELEASE"] = strconv.FormatBool(m.Curr.Commit.IsPrerelease)
133+
}
131134
if m.Curr.Event == EventPull {
132135
params["CI_COMMIT_PULL_REQUEST"] = pullRegexp.FindString(m.Curr.Commit.Ref)
133136
params["CI_COMMIT_PULL_REQUEST_LABELS"] = strings.Join(m.Curr.Commit.PullRequestLabels, ",")

pipeline/frontend/metadata/types.go

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ type (
6969
Author Author `json:"author,omitempty"`
7070
ChangedFiles []string `json:"changed_files,omitempty"`
7171
PullRequestLabels []string `json:"labels,omitempty"`
72+
IsPrerelease bool `json:"is_prerelease,omitempty"`
7273
}
7374

7475
// Author defines runtime metadata for a commit author.

server/api/pipeline.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ func PostPipeline(c *gin.Context) {
414414
if event, ok := c.GetQuery("event"); ok {
415415
pl.Event = model.WebhookEvent(event)
416416

417-
if err := model.ValidateWebhookEvent(pl.Event); err != nil {
417+
if err := pl.Event.Validate(); err != nil {
418418
_ = c.AbortWithError(http.StatusBadRequest, err)
419419
return
420420
}

server/forge/gitea/fixtures/hooks.go

+76
Original file line numberDiff line numberDiff line change
@@ -1071,3 +1071,79 @@ const HookPullRequestClosed = `
10711071
"review": null
10721072
}
10731073
`
1074+
1075+
const HookRelease = `
1076+
{
1077+
"action": "published",
1078+
"release": {
1079+
"id": 48,
1080+
"tag_name": "0.0.5",
1081+
"target_commitish": "main",
1082+
"name": "Version 0.0.5",
1083+
"body": "",
1084+
"url": "https://git.xxx/api/v1/repos/anbraten/demo/releases/48",
1085+
"html_url": "https://git.xxx/anbraten/demo/releases/tag/0.0.5",
1086+
"tarball_url": "https://git.xxx/anbraten/demo/archive/0.0.5.tar.gz",
1087+
"zipball_url": "https://git.xxx/anbraten/demo/archive/0.0.5.zip",
1088+
"draft": false,
1089+
"prerelease": false,
1090+
"created_at": "2022-02-09T20:23:05Z",
1091+
"published_at": "2022-02-09T20:23:05Z",
1092+
"author": {"id":1,"login":"anbraten","full_name":"Anton Bracke","email":"anbraten@noreply.xxx","avatar_url":"https://git.xxx/user/avatar/anbraten/-1","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2018-03-21T10:04:48Z","restricted":false,"active":false,"prohibit_login":false,"location":"world","website":"https://xxx","description":"","visibility":"public","followers_count":1,"following_count":1,"starred_repos_count":1,"username":"anbraten"},
1093+
"assets": []
1094+
},
1095+
"repository": {
1096+
"id": 77,
1097+
"owner": {"id":1,"login":"anbraten","full_name":"Anton Bracke","email":"anbraten@noreply.xxx","avatar_url":"https://git.xxx/user/avatar/anbraten/-1","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2018-03-21T10:04:48Z","restricted":false,"active":false,"prohibit_login":false,"location":"world","website":"https://xxx","description":"","visibility":"public","followers_count":1,"following_count":1,"starred_repos_count":1,"username":"anbraten"},
1098+
"name": "demo",
1099+
"full_name": "anbraten/demo",
1100+
"description": "",
1101+
"empty": false,
1102+
"private": true,
1103+
"fork": false,
1104+
"template": false,
1105+
"parent": null,
1106+
"mirror": false,
1107+
"size": 59,
1108+
"html_url": "https://git.xxx/anbraten/demo",
1109+
"ssh_url": "ssh://git@git.xxx:22/anbraten/demo.git",
1110+
"clone_url": "https://git.xxx/anbraten/demo.git",
1111+
"original_url": "",
1112+
"website": "",
1113+
"stars_count": 0,
1114+
"forks_count": 1,
1115+
"watchers_count": 1,
1116+
"open_issues_count": 2,
1117+
"open_pr_counter": 2,
1118+
"release_counter": 4,
1119+
"default_branch": "main",
1120+
"archived": false,
1121+
"created_at": "2021-08-30T20:54:13Z",
1122+
"updated_at": "2022-01-09T01:29:23Z",
1123+
"permissions": {
1124+
"admin": true,
1125+
"push": true,
1126+
"pull": true
1127+
},
1128+
"has_issues": true,
1129+
"internal_tracker": {
1130+
"enable_time_tracker": true,
1131+
"allow_only_contributors_to_track_time": true,
1132+
"enable_issue_dependencies": true
1133+
},
1134+
"has_wiki": false,
1135+
"has_pull_requests": true,
1136+
"has_projects": true,
1137+
"ignore_whitespace_conflicts": false,
1138+
"allow_merge_commits": true,
1139+
"allow_rebase": true,
1140+
"allow_rebase_explicit": true,
1141+
"allow_squash_merge": true,
1142+
"default_merge_style": "squash",
1143+
"avatar_url": "",
1144+
"internal": false,
1145+
"mirror_interval": ""
1146+
},
1147+
"sender": {"id":1,"login":"anbraten","full_name":"Anbraten","email":"anbraten@noreply.xxx","avatar_url":"https://git.xxx/user/avatar/anbraten/-1","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2018-03-21T10:04:48Z","restricted":false,"active":false,"prohibit_login":false,"location":"World","website":"https://xxx","description":"","visibility":"public","followers_count":1,"following_count":1,"starred_repos_count":1,"username":"anbraten"}
1148+
}
1149+
`

server/forge/gitea/gitea.go

+39
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,15 @@ func (c *Gitea) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.
510510
return nil, nil, err
511511
}
512512

513+
if pipeline != nil && pipeline.Event == model.EventRelease && pipeline.Commit == "" {
514+
tagName := strings.Split(pipeline.Ref, "/")[2]
515+
sha, err := c.getTagCommitSHA(ctx, repo, tagName)
516+
if err != nil {
517+
return nil, nil, err
518+
}
519+
pipeline.Commit = sha
520+
}
521+
513522
if pipeline != nil && (pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed) && len(pipeline.ChangedFiles) == 0 {
514523
index, err := strconv.ParseInt(strings.Split(pipeline.Ref, "/")[2], 10, 64)
515524
if err != nil {
@@ -655,6 +664,36 @@ func (c *Gitea) getChangedFilesForPR(ctx context.Context, repo *model.Repo, inde
655664
})
656665
}
657666

667+
func (c *Gitea) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName string) (string, error) {
668+
_store, ok := store.TryFromContext(ctx)
669+
if !ok {
670+
log.Error().Msg("could not get store from context")
671+
return "", nil
672+
}
673+
674+
repo, err := _store.GetRepoNameFallback(repo.ForgeRemoteID, repo.FullName)
675+
if err != nil {
676+
return "", err
677+
}
678+
679+
user, err := _store.GetUser(repo.UserID)
680+
if err != nil {
681+
return "", err
682+
}
683+
684+
client, err := c.newClientToken(ctx, user.Token)
685+
if err != nil {
686+
return "", err
687+
}
688+
689+
tag, _, err := client.GetTag(repo.Owner, repo.Name, tagName)
690+
if err != nil {
691+
return "", err
692+
}
693+
694+
return tag.Commit.SHA, nil
695+
}
696+
658697
func (c *Gitea) perPage(ctx context.Context) int {
659698
if c.pageSize == 0 {
660699
client, err := c.newClientToken(ctx, "")

server/forge/gitea/helper.go

+25
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,25 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline {
175175
return pipeline
176176
}
177177

178+
func pipelineFromRelease(hook *releaseHook) *model.Pipeline {
179+
avatar := expandAvatar(
180+
hook.Repo.HTMLURL,
181+
fixMalformedAvatar(hook.Sender.AvatarURL),
182+
)
183+
184+
return &model.Pipeline{
185+
Event: model.EventRelease,
186+
Ref: fmt.Sprintf("refs/tags/%s", hook.Release.TagName),
187+
ForgeURL: hook.Release.HTMLURL,
188+
Branch: hook.Release.Target,
189+
Message: fmt.Sprintf("created release %s", hook.Release.Title),
190+
Avatar: avatar,
191+
Author: hook.Sender.UserName,
192+
Sender: hook.Sender.UserName,
193+
IsPrerelease: hook.Release.IsPrerelease,
194+
}
195+
}
196+
178197
// helper function that parses a push hook from a read closer.
179198
func parsePush(r io.Reader) (*pushHook, error) {
180199
push := new(pushHook)
@@ -188,6 +207,12 @@ func parsePullRequest(r io.Reader) (*pullRequestHook, error) {
188207
return pr, err
189208
}
190209

210+
func parseRelease(r io.Reader) (*releaseHook, error) {
211+
pr := new(releaseHook)
212+
err := json.NewDecoder(r).Decode(pr)
213+
return pr, err
214+
}
215+
191216
// fixMalformedAvatar is a helper function that fixes an avatar url if malformed
192217
// (currently a known bug with gitea)
193218
func fixMalformedAvatar(url string) string {

server/forge/gitea/parse.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131
hookPush = "push"
3232
hookCreated = "create"
3333
hookPullRequest = "pull_request"
34+
hookRelease = "release"
3435

3536
actionOpen = "opened"
3637
actionSync = "synchronized"
@@ -40,7 +41,7 @@ const (
4041
refTag = "tag"
4142
)
4243

43-
// parseHook parses a Gitea hook from an http.Request request and returns
44+
// parseHook parses a Gitea hook from an http.Request and returns
4445
// Repo and Pipeline detail. If a hook type is unsupported nil values are returned.
4546
func parseHook(r *http.Request) (*model.Repo, *model.Pipeline, error) {
4647
hookType := r.Header.Get(hookEvent)
@@ -51,6 +52,8 @@ func parseHook(r *http.Request) (*model.Repo, *model.Pipeline, error) {
5152
return parseCreatedHook(r.Body)
5253
case hookPullRequest:
5354
return parsePullRequestHook(r.Body)
55+
case hookRelease:
56+
return parseReleaseHook(r.Body)
5457
}
5558
log.Debug().Msgf("unsupported hook type: '%s'", hookType)
5659
return nil, nil, &types.ErrIgnoreEvent{Event: hookType}
@@ -118,3 +121,20 @@ func parsePullRequestHook(payload io.Reader) (*model.Repo, *model.Pipeline, erro
118121
pipeline = pipelineFromPullRequest(pr)
119122
return repo, pipeline, err
120123
}
124+
125+
// parseReleaseHook parses a release hook and returns the Repo and Pipeline details.
126+
func parseReleaseHook(payload io.Reader) (*model.Repo, *model.Pipeline, error) {
127+
var (
128+
repo *model.Repo
129+
pipeline *model.Pipeline
130+
)
131+
132+
release, err := parseRelease(payload)
133+
if err != nil {
134+
return nil, nil, err
135+
}
136+
137+
repo = toRepo(release.Repo)
138+
pipeline = pipelineFromRelease(release)
139+
return repo, pipeline, err
140+
}

server/forge/gitea/parse_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ func Test_parser(t *testing.T) {
124124
g.Assert(err).IsNil()
125125
g.Assert(b.Event).Equal(model.EventPullClosed)
126126
})
127+
g.It("should handle release hook", func() {
128+
buf := bytes.NewBufferString(fixtures.HookRelease)
129+
req, _ := http.NewRequest("POST", "/hook", buf)
130+
req.Header = http.Header{}
131+
req.Header.Set(hookEvent, hookRelease)
132+
r, b, err := parseHook(req)
133+
g.Assert(err).IsNil()
134+
g.Assert(r).IsNotNil()
135+
g.Assert(b).IsNotNil()
136+
g.Assert(b.Event).Equal(model.EventRelease)
137+
})
127138
})
128139
})
129140
}

server/forge/gitea/types.go

+7
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,10 @@ type pullRequestHook struct {
4343
Repo *gitea.Repository `json:"repository"`
4444
Sender *gitea.User `json:"sender"`
4545
}
46+
47+
type releaseHook struct {
48+
Action string `json:"action"`
49+
Repo *gitea.Repository `json:"repository"`
50+
Sender *gitea.User `json:"sender"`
51+
Release *gitea.Release
52+
}

0 commit comments

Comments
 (0)