From 2e23e550091617563c2d4def3b2d0f5b58642272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernhard=20Gr=C3=BCnewaldt?= Date: Thu, 24 Aug 2017 16:42:43 +0200 Subject: [PATCH] 1.1.0 support pipeline jobs. support gh webhook ping event. --- DEVELOPMENT.md | 32 +++- README.md | 31 ++++ pom.xml | 14 +- .../EnvironmentContributionAction.java | 24 ++- .../GithubWebhookBuildTriggerAction.java | 49 ++++++- .../GithubWebhookPayload.java | 13 ++ test-webhook-init-payload.json | 137 ++++++++++++++++++ 7 files changed, 286 insertions(+), 14 deletions(-) create mode 100644 test-webhook-init-payload.json diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 3a044f5..69129ce 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,7 +2,13 @@ ```bash mvn compile && rm -rf work/ && mvn hpi:run +// configure GitHub Secret to `foobar23` via webui +``` + + +**Testcase: Push Event** +```bash curl -X POST \ -H "Content-Type: application/json" \ -H "x-hub-signature: sha1=5b8a54ee48efb1fbe06e3e4fc5d120680eaa2a22" \ @@ -10,7 +16,31 @@ curl -X POST \ http://localhost:8080/jenkins/github-webhook-build-trigger/receive ``` - * With GitHub Secret being: `foobar23` +  + + +**Testcase: Ping Event** + +Initial webhook created request. +Only fired once when you setup the webhook on github. See: https://developer.github.com/webhooks/#ping-event + +```bash +curl -X POST \ + -H "Content-Type: application/json" \ + -H "x-hub-signature: sha1=c203f51e9317264c6716ee0c6fed59d604674885" \ + -d @test-webhook-init-payload.json \ + http://localhost:8080/jenkins/github-webhook-build-trigger/receive +``` + +  + +**x-hub-signature** + +Create an sha1 hash for a payload.json: + +```bash +cat test-webhook-init-payload.json | openssl dgst -sha1 -hmac "foobar23" +``` ### Build hpi diff --git a/README.md b/README.md index 4d0b00d..b50ba07 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,37 @@ git clone --single-branch \ source ``` +  + +**Usage with Pipeline Jobs** + +Since version 1.1.0 [Pipeline Job Types](https://jenkins.io/doc/book/pipeline/) (NOT MultiBranch Pipeline) are supported. + +Make sure you have at least the following Plugins installed + +``` +Pipeline: Groovy +Pipeline: Job +Pipeline: API +Pipeline: Step API +Pipeline: Stage Step +Pipeline: Basic Steps +Pipeline: Model API +``` + +A simple **`Jenkinsfile`** could look like this: + +``` +node { + stage('foo') { + sh 'git clone --single-branch --branch ${env.GWBT_BRANCH} https://github.com/${env.GWBT_REPO_FULL_NAME}.git source' + dir('source') { + sh 'npm install' + } + } +} +``` + -----   diff --git a/pom.xml b/pom.xml index ce37e8b..32b2e39 100644 --- a/pom.xml +++ b/pom.xml @@ -6,12 +6,12 @@ org.jenkins-ci.plugins plugin - 2.25 + 2.33 io.codeclou.jenkins.github.webhook.notifier.plugin github-webhook-notifier-plugin - 1.0.1 + 1.1.0 hpi github-webhook-notifier-plugin @@ -52,5 +52,15 @@ commons-io 2.5 + + org.jenkins-ci.plugins.workflow + workflow-job + 2.5 + true + + + + 2.73 + diff --git a/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/EnvironmentContributionAction.java b/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/EnvironmentContributionAction.java index 7d4378b..16d96a7 100644 --- a/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/EnvironmentContributionAction.java +++ b/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/EnvironmentContributionAction.java @@ -5,10 +5,11 @@ package io.codeclou.jenkins.githubwebhookbuildtriggerplugin; import hudson.EnvVars; -import hudson.model.AbstractBuild; -import hudson.model.EnvironmentContributingAction; +import hudson.model.*; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /* @@ -48,7 +49,7 @@ public EnvironmentContributionAction(GithubWebhookPayload payload) { * converts "refs/heads/develop" to "develop" */ private String normalizeBranchNameOrEmptyString(String branchname) { - if (branchname.startsWith("refs/heads/")) { + if (branchname != null && branchname.startsWith("refs/heads/")) { return branchname.replace("refs/heads/", ""); } return ""; @@ -58,7 +59,7 @@ private String normalizeBranchNameOrEmptyString(String branchname) { * converts "refs/tags/1.0.0" to "1.0.0" */ private String normalizeTagNameOrEmptyString(String tagname) { - if (tagname.startsWith("refs/tags/")) { + if (tagname != null && tagname.startsWith("refs/tags/")) { return tagname.replace("refs/tags/", ""); } return ""; @@ -90,4 +91,19 @@ public void buildEnvVars(AbstractBuild build, EnvVars env) { env.putAll(environmentVariables); } } + + /** + * Since WorkflowJob does not support EnvironmentContributionAction yet, + * we need a ParametersAction filled with List ParameterValue + * See: https://github.com/jenkinsci/workflow-job-plugin/blob/124b171b76394728f9c8504829cf6857abc8bdb5/src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowRun.java#L435 + */ + public ParametersAction transform() { + List paramValues = new ArrayList<>(); + List safeParams = new ArrayList<>(); + for (Map.Entry envVar : environmentVariables.entrySet()) { + paramValues.add(new StringParameterValue(envVar.getKey(), envVar.getValue(), envVar.getValue())); + safeParams.add(envVar.getKey()); + } + return new ParametersAction(paramValues, safeParams); + } } diff --git a/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/GithubWebhookBuildTriggerAction.java b/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/GithubWebhookBuildTriggerAction.java index 79a2f1b..51647bf 100644 --- a/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/GithubWebhookBuildTriggerAction.java +++ b/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/GithubWebhookBuildTriggerAction.java @@ -6,27 +6,30 @@ import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; +import hudson.EnvVars; import hudson.Extension; import hudson.model.*; +import hudson.model.queue.QueueTaskFuture; import hudson.util.HttpResponses; import io.codeclou.jenkins.githubwebhookbuildtriggerplugin.config.GithubWebhookBuildTriggerPluginBuilder; import io.codeclou.jenkins.githubwebhookbuildtriggerplugin.webhooksecret.GitHubWebhookUtility; import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.interceptor.RequirePOST; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; -import javax.swing.plaf.basic.BasicToolTipUI; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; @Extension -public class GithubWebhookBuildTriggerAction implements UnprotectedRootAction { +public class GithubWebhookBuildTriggerAction implements UnprotectedRootAction, EnvironmentContributingAction { @Override public String getUrlName() { @@ -65,7 +68,7 @@ public HttpResponse doReceive(HttpServletRequest request, StaplerRequest stapler String webhookSecretAsConfiguredByUser = GithubWebhookBuildTriggerPluginBuilder.DescriptorImpl.getDescriptor().getWebhookSecret(); String webhookSecretMessage ="validating webhook payload against wevhook secret."; info.append(">> webhook secret validation").append("\n"); - if (webhookSecretAsConfiguredByUser == null) { + if (webhookSecretAsConfiguredByUser == null || webhookSecretAsConfiguredByUser.isEmpty()) { webhookSecretMessage = " skipping validation since no webhook secret is configured in \n" + " 'Jenkins' -> 'Configure' tab under 'Github Webhook Build Trigger' section."; } else { @@ -77,11 +80,23 @@ public HttpResponse doReceive(HttpServletRequest request, StaplerRequest stapler webhookSecretMessage = " ok. Webhook secret validates against " + githubSignature + "\n"; } info.append(webhookSecretMessage).append("\n\n"); + // - // PAYLOAD TO ENVVARS + // CHECK IF INITIAL REQUEST (see test-webhook-init-payload.json) + // See: https://developer.github.com/webhooks/#ping-event // + if (githubWebhookPayload.getHook_id() != null) { + info.append(">> ping request received: your webhook with ID "); + info.append(githubWebhookPayload.getHook_id()); + info.append(" is working :)\n"); + return HttpResponses.plainText(this.getTextEnvelopedInBanner(info.toString())); + } + // + // PAYLOAD TO ENVVARS + // EnvironmentContributionAction environmentContributionAction = new EnvironmentContributionAction(githubWebhookPayload); + // // TRIGGER JOBS // @@ -103,10 +118,25 @@ public HttpResponse doReceive(HttpServletRequest request, StaplerRequest stapler } for (Job job: jobs) { if (job.getName().startsWith(jobNamePrefix) && ! jobsAlreadyTriggered.contains(job.getName())) { - jobsTriggered.append(" ").append(job.getName()).append("\n"); jobsAlreadyTriggered.add(job.getName()); - AbstractProject projectScheduable = (AbstractProject) job; - projectScheduable.scheduleBuild(0, cause, environmentContributionAction); + if (job instanceof WorkflowJob) { + WorkflowJob wjob = (WorkflowJob) job; + if (wjob.isBuildable()) { + jobsTriggered.append(" WORKFLOWJOB> ").append(job.getName()).append(" TRIGGERED\n"); + wjob.addAction(environmentContributionAction.transform()); + wjob.scheduleBuild(0, cause); + } else { + jobsTriggered.append(" WORKFLOWJOB> ").append(job.getName()).append(" NOT BUILDABLE. SKIPPING.\n"); + } + } else { + AbstractProject projectScheduable = (AbstractProject) job; + if (job.isBuildable()) { + jobsTriggered.append(" CLASSICJOB> ").append(job.getName()).append(" TRIGGERED\n"); + projectScheduable.scheduleBuild(0, cause, environmentContributionAction); + } else { + jobsTriggered.append(" CLASSICJOB> ").append(job.getName()).append(" NOT BUILDABLE. SKIPPING.\n"); + } + } } } // @@ -139,4 +169,9 @@ private String getTextEnvelopedInBanner(String text) { banner.append("\n----------------------------------------------------------------------------------\n"); return banner.toString(); } + + @Override + public void buildEnvVars(AbstractBuild abstractBuild, EnvVars envVars) { + + } } diff --git a/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/GithubWebhookPayload.java b/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/GithubWebhookPayload.java index 14d29fa..f9dddc4 100644 --- a/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/GithubWebhookPayload.java +++ b/src/main/java/io/codeclou/jenkins/githubwebhookbuildtriggerplugin/GithubWebhookPayload.java @@ -10,6 +10,11 @@ */ public class GithubWebhookPayload { + /* + * hook_id is only set on initial request when the webhook is created. + * See: https://developer.github.com/webhooks/#ping-event + */ + private Long hook_id; private String ref; private String before; private String after; @@ -51,6 +56,14 @@ public void setRepository(GithubWebhookPayloadRepository repository) { this.repository = repository; } + public Long getHook_id() { + return hook_id; + } + + public void setHook_id(Long hook_id) { + this.hook_id = hook_id; + } + public class GithubWebhookPayloadRepository { private String clone_url; private String html_url; diff --git a/test-webhook-init-payload.json b/test-webhook-init-payload.json new file mode 100644 index 0000000..29d3402 --- /dev/null +++ b/test-webhook-init-payload.json @@ -0,0 +1,137 @@ +{ + "zen": "Half measures are as bad as nothing at all.", + "hook_id": 15715749, + "hook": { + "type": "Repository", + "id": 15715749, + "name": "web", + "active": true, + "events": [ + "push" + ], + "config": { + "content_type": "json", + "insecure_ssl": "0", + "secret": "********", + "url": "https://foo.bar/jenkins-github-webhook-build-trigger/receive" + }, + "updated_at": "2017-08-24T11:53:44Z", + "created_at": "2017-08-24T11:53:44Z", + "url": "https://api.github.com/repos/clouless/test123/hooks/15715749", + "test_url": "https://api.github.com/repos/clouless/test123/hooks/15715749/test", + "ping_url": "https://api.github.com/repos/clouless/test123/hooks/15715749/pings", + "last_response": { + "code": null, + "status": "unused", + "message": null + } + }, + "repository": { + "id": 101287239, + "name": "test123", + "full_name": "clouless/test123", + "owner": { + "login": "clouless", + "id": 12599965, + "avatar_url": "https://avatars0.githubusercontent.com/u/12599965?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/clouless", + "html_url": "https://github.com/clouless", + "followers_url": "https://api.github.com/users/clouless/followers", + "following_url": "https://api.github.com/users/clouless/following{/other_user}", + "gists_url": "https://api.github.com/users/clouless/gists{/gist_id}", + "starred_url": "https://api.github.com/users/clouless/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/clouless/subscriptions", + "organizations_url": "https://api.github.com/users/clouless/orgs", + "repos_url": "https://api.github.com/users/clouless/repos", + "events_url": "https://api.github.com/users/clouless/events{/privacy}", + "received_events_url": "https://api.github.com/users/clouless/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/clouless/test123", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/clouless/test123", + "forks_url": "https://api.github.com/repos/clouless/test123/forks", + "keys_url": "https://api.github.com/repos/clouless/test123/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/clouless/test123/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/clouless/test123/teams", + "hooks_url": "https://api.github.com/repos/clouless/test123/hooks", + "issue_events_url": "https://api.github.com/repos/clouless/test123/issues/events{/number}", + "events_url": "https://api.github.com/repos/clouless/test123/events", + "assignees_url": "https://api.github.com/repos/clouless/test123/assignees{/user}", + "branches_url": "https://api.github.com/repos/clouless/test123/branches{/branch}", + "tags_url": "https://api.github.com/repos/clouless/test123/tags", + "blobs_url": "https://api.github.com/repos/clouless/test123/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/clouless/test123/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/clouless/test123/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/clouless/test123/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/clouless/test123/statuses/{sha}", + "languages_url": "https://api.github.com/repos/clouless/test123/languages", + "stargazers_url": "https://api.github.com/repos/clouless/test123/stargazers", + "contributors_url": "https://api.github.com/repos/clouless/test123/contributors", + "subscribers_url": "https://api.github.com/repos/clouless/test123/subscribers", + "subscription_url": "https://api.github.com/repos/clouless/test123/subscription", + "commits_url": "https://api.github.com/repos/clouless/test123/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/clouless/test123/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/clouless/test123/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/clouless/test123/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/clouless/test123/contents/{+path}", + "compare_url": "https://api.github.com/repos/clouless/test123/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/clouless/test123/merges", + "archive_url": "https://api.github.com/repos/clouless/test123/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/clouless/test123/downloads", + "issues_url": "https://api.github.com/repos/clouless/test123/issues{/number}", + "pulls_url": "https://api.github.com/repos/clouless/test123/pulls{/number}", + "milestones_url": "https://api.github.com/repos/clouless/test123/milestones{/number}", + "notifications_url": "https://api.github.com/repos/clouless/test123/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/clouless/test123/labels{/name}", + "releases_url": "https://api.github.com/repos/clouless/test123/releases{/id}", + "deployments_url": "https://api.github.com/repos/clouless/test123/deployments", + "created_at": "2017-08-24T11:22:34Z", + "updated_at": "2017-08-24T11:22:34Z", + "pushed_at": "2017-08-24T11:22:35Z", + "git_url": "git://github.com/clouless/test123.git", + "ssh_url": "git@github.com:clouless/test123.git", + "clone_url": "https://github.com/clouless/test123.git", + "svn_url": "https://github.com/clouless/test123", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "clouless", + "id": 12599965, + "avatar_url": "https://avatars0.githubusercontent.com/u/12599965?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/clouless", + "html_url": "https://github.com/clouless", + "followers_url": "https://api.github.com/users/clouless/followers", + "following_url": "https://api.github.com/users/clouless/following{/other_user}", + "gists_url": "https://api.github.com/users/clouless/gists{/gist_id}", + "starred_url": "https://api.github.com/users/clouless/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/clouless/subscriptions", + "organizations_url": "https://api.github.com/users/clouless/orgs", + "repos_url": "https://api.github.com/users/clouless/repos", + "events_url": "https://api.github.com/users/clouless/events{/privacy}", + "received_events_url": "https://api.github.com/users/clouless/received_events", + "type": "User", + "site_admin": false + } +}