├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── apitoken.png ├── dockerhub.png ├── .mvn ├── maven.config └── extensions.xml ├── src ├── test │ ├── resources │ │ ├── org │ │ │ └── jenkinsci │ │ │ │ └── plugins │ │ │ │ └── registry │ │ │ │ └── notification │ │ │ │ └── BackCompat102Test │ │ │ │ ├── jobs │ │ │ │ ├── JenkinsSlaveTrigger │ │ │ │ │ ├── nextBuildNumber │ │ │ │ │ ├── builds │ │ │ │ │ │ ├── 1 │ │ │ │ │ │ │ ├── changelog.xml │ │ │ │ │ │ │ ├── log │ │ │ │ │ │ │ └── build.xml │ │ │ │ │ │ ├── 2 │ │ │ │ │ │ │ ├── changelog.xml │ │ │ │ │ │ │ ├── log │ │ │ │ │ │ │ └── build.xml │ │ │ │ │ │ ├── lastFailedBuild │ │ │ │ │ │ └── lastUnstableBuild │ │ │ │ │ ├── lastStable │ │ │ │ │ └── config.xml │ │ │ │ └── OneConfigured │ │ │ │ │ ├── builds │ │ │ │ │ ├── lastFailedBuild │ │ │ │ │ ├── lastStableBuild │ │ │ │ │ ├── lastSuccessfulBuild │ │ │ │ │ ├── lastUnstableBuild │ │ │ │ │ └── lastUnsuccessfulBuild │ │ │ │ │ └── config.xml │ │ │ │ └── fingerprints │ │ │ │ ├── 58 │ │ │ │ └── 73 │ │ │ │ │ └── 052b0750060b97b428e5dbdd1c03.xml │ │ │ │ └── e9 │ │ │ │ └── d6 │ │ │ │ └── eb6cd6a7bfcd2bd622765a87893f.xml │ │ ├── jcasc_bare.yaml │ │ ├── jcasc_symbols.yaml │ │ ├── acr-payload-valid.json │ │ ├── private-registry-payload-1-repository.json │ │ ├── private-registry-payload-2-repositories.json │ │ ├── private-registry-payload-blob-1-repository.json │ │ ├── private-registry-payload-pull-1-repository.json │ │ ├── own-repository-payload.json │ │ └── public-repository-payload.json │ └── java │ │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── registry │ │ └── notification │ │ ├── webhook │ │ └── WebHookPayloadTest.java │ │ ├── DockerHubTriggerTest.java │ │ ├── JcasCTest.java │ │ ├── ACRWebHookTest.java │ │ └── DockerHubWebHookTest.java └── main │ ├── resources │ ├── org │ │ └── jenkinsci │ │ │ └── plugins │ │ │ └── registry │ │ │ └── notification │ │ │ ├── webhook │ │ │ └── ResultPage │ │ │ │ ├── docker_128.png │ │ │ │ ├── docker_128_grey.png │ │ │ │ ├── index.properties │ │ │ │ ├── main.properties │ │ │ │ ├── NoResultPage │ │ │ │ ├── main.properties │ │ │ │ └── main.groovy │ │ │ │ ├── index.groovy │ │ │ │ ├── main.groovy │ │ │ │ └── style.css │ │ │ ├── token │ │ │ └── ApiTokens │ │ │ │ ├── help-tokens.html │ │ │ │ ├── resources.css │ │ │ │ ├── config.jelly │ │ │ │ └── resources.js │ │ │ ├── TriggerListViewColumn │ │ │ ├── columnHeader.properties │ │ │ ├── help-showMax.html │ │ │ ├── columnHeader.groovy │ │ │ ├── config.groovy │ │ │ └── column.groovy │ │ │ ├── opt │ │ │ └── impl │ │ │ │ ├── TriggerForAllUsedInJob │ │ │ │ ├── help.properties │ │ │ │ └── help.groovy │ │ │ │ └── TriggerOnSpecifiedImageNames │ │ │ │ └── config.groovy │ │ │ ├── TriggerViewFilter │ │ │ ├── help.html │ │ │ ├── config.groovy │ │ │ └── help-patterns.html │ │ │ ├── DockerPullImageBuilder │ │ │ ├── help-image.html │ │ │ └── config.jelly │ │ │ ├── DockerHubTrigger │ │ │ ├── help.properties │ │ │ ├── config.groovy │ │ │ └── help.groovy │ │ │ └── Messages.properties │ └── index.jelly │ └── java │ └── org │ └── jenkinsci │ └── plugins │ └── registry │ └── notification │ ├── webhook │ ├── acr │ │ ├── ACRWebHookCause.java │ │ ├── ACRWebHook.java │ │ ├── ACRWebHookPayload.java │ │ └── ACRPushNotification.java │ ├── CallbackHandler.java │ ├── WebHookCause.java │ ├── dockerregistry │ │ ├── DockerRegistryWebHookCause.java │ │ ├── DockerRegistryWebHook.java │ │ ├── DockerRegistryWebHookPayload.java │ │ └── DockerRegistryPushNotification.java │ ├── dockerhub │ │ ├── DockerHubWebHookCause.java │ │ ├── DockerHubWebHook.java │ │ └── DockerHubWebHookPayload.java │ ├── ResultPage.java │ ├── Http.java │ ├── WebHookCrumbExclusion.java │ ├── WebHookPayload.java │ └── PushNotification.java │ ├── opt │ ├── TriggerOption.java │ ├── TriggerOptionDescriptor.java │ └── impl │ │ ├── TriggerForAllUsedInJob.java │ │ └── TriggerOnSpecifiedImageNames.java │ ├── EnvContributor.java │ ├── TriggerImageExtractor.java │ ├── TriggerListViewColumn.java │ ├── Coordinator.java │ └── DockerPullImageBuilder.java ├── Jenkinsfile ├── .gitignore ├── LICENCE.txt ├── TODO.md ├── README.md ├── CHANGES.md └── pom.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/dockerhub-notification-plugin-developers 2 | -------------------------------------------------------------------------------- /apitoken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/dockerhub-notification-plugin/HEAD/apitoken.png -------------------------------------------------------------------------------- /dockerhub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/dockerhub-notification-plugin/HEAD/dockerhub.png -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/nextBuildNumber: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/OneConfigured/builds/lastFailedBuild: -------------------------------------------------------------------------------- 1 | -1 -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/OneConfigured/builds/lastStableBuild: -------------------------------------------------------------------------------- 1 | -1 -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/OneConfigured/builds/lastSuccessfulBuild: -------------------------------------------------------------------------------- 1 | -1 -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/OneConfigured/builds/lastUnstableBuild: -------------------------------------------------------------------------------- 1 | -1 -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/builds/lastFailedBuild: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/builds/lastUnstableBuild: -------------------------------------------------------------------------------- 1 | -1 -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/OneConfigured/builds/lastUnsuccessfulBuild: -------------------------------------------------------------------------------- 1 | -1 -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/builds/1/changelog.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/builds/2/changelog.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/lastStable: -------------------------------------------------------------------------------- 1 | builds/lastStableBuild -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | buildPlugin(useContainerAgent: true, configurations: [ 2 | [platform: 'linux', jdk: 21], 3 | [platform: 'windows', jdk: 17], 4 | ]) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | # mvn hpi:run 4 | work 5 | 6 | # IntelliJ IDEA project files 7 | *.iml 8 | *.iws 9 | *.ipr 10 | .idea 11 | 12 | # Eclipse project files 13 | .settings 14 | .classpath 15 | .project 16 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/docker_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/dockerhub-notification-plugin/HEAD/src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/docker_128.png -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/docker_128_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/dockerhub-notification-plugin/HEAD/src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/docker_128_grey.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 2 | --- 3 | version: 2 4 | updates: 5 | - package-ecosystem: "maven" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.8 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/builds/1/log: -------------------------------------------------------------------------------- 1 | Triggered by push of csanchez/jenkins-swarm-slave to DockerHub@registry.hub.example.com 2 | Building in workspace /var/jenkins_home/workspace/JenkinsSlaveTrigger 3 | [JenkinsSlaveTrigger] $ /bin/sh -xe /tmp/hudson5038252045818174291.sh 4 | + env sort 5 | + grep DOCKER 6 | Build step 'Execute shell' marked build as failure 7 | Finished: FAILURE 8 | -------------------------------------------------------------------------------- /src/test/resources/jcasc_bare.yaml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | views: 3 | - list: 4 | columns: 5 | - "status" 6 | - "weather" 7 | - "jobName" 8 | - trigger: 9 | showMax: 3 10 | - "lastSuccess" 11 | - "lastFailure" 12 | - "lastDuration" 13 | - "buildButton" 14 | jobFilters: 15 | - triggerViewFilter: 16 | patterns: 17 | - ".*" 18 | - "jenkins" 19 | name: "Docker" 20 | - all: 21 | name: "all" 22 | viewsTabBar: "standard" 23 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/builds/2/log: -------------------------------------------------------------------------------- 1 | Triggered by push of csanchez/jenkins-swarm-slave to DockerHub@registry.hub.example.com 2 | Building in workspace /var/jenkins_home/workspace/JenkinsSlaveTrigger 3 | [JenkinsSlaveTrigger] $ /bin/sh -xe /tmp/hudson7891375838265745811.sh 4 | + env 5 | + grep DOCKER 6 | + sort 7 | DOCKER_TRIGGER_DOCKER_HUB_HOST=registry.hub.example.com 8 | DOCKER_TRIGGER_REPO_NAME=csanchez/jenkins-swarm-slave 9 | Finished: SUCCESS 10 | -------------------------------------------------------------------------------- /src/test/resources/jcasc_symbols.yaml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | views: 3 | - list: 4 | columns: 5 | - "status" 6 | - "weather" 7 | - "jobName" 8 | - dockerImageNames: 9 | showMax: 3 10 | - "lastSuccess" 11 | - "lastFailure" 12 | - "lastDuration" 13 | - "buildButton" 14 | jobFilters: 15 | - dockerTriggers: 16 | patterns: 17 | - ".*" 18 | - "jenkins" 19 | name: "Docker" 20 | - all: 21 | name: "all" 22 | viewsTabBar: "standard" 23 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | --- 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | permissions: 11 | checks: read 12 | contents: write 13 | 14 | jobs: 15 | maven-cd: 16 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 17 | secrets: 18 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 19 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 20 | -------------------------------------------------------------------------------- /src/test/resources/acr-payload-valid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "52ba983c-67d6-11e8-8c71-4a0006ae5a20", 3 | "timestamp": "2018-06-04T09:04:00.000000000Z", 4 | "action": "push", 5 | "target": { 6 | "mediaType": "application/vnd.docker.distribution.manifest.v2+json", 7 | "size": 524, 8 | "digest": "sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77", 9 | "length": 524, 10 | "repository": "hello-world", 11 | "tag": "v1" 12 | }, 13 | "request": { 14 | "id": "5d97eae8-67d6-11e8-b1ec-4a0006ae5a20", 15 | "host": "myregistry.azurecr.io", 16 | "method": "PUT", 17 | "useragent": "some user agent" 18 | } 19 | } -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/acr/ACRWebHookCause.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.registry.notification.webhook.acr; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookCause; 5 | 6 | 7 | public class ACRWebHookCause extends WebHookCause { 8 | public ACRWebHookCause(@NonNull ACRPushNotification notification) { 9 | super(notification); 10 | } 11 | 12 | @Override 13 | public String getShortDescription() { 14 | return String.format("Triggered by %s", getPushNotification().getShortDescription()); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "ACRWebHookCause{" + 20 | "payload=" + getPushNotification().getWebHookPayload() + 21 | '}'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/token/ApiTokens/help-tokens.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | These access tokens serve as a way of authenticating requests to the 4 | dockerhub-webhook, dockerregistry-webhook and acr-webhook endpoints. 5 |

6 |

7 | By default, all requests to those endpoints must include a valid token in the path component before /notify. 8 | E.g. https://jenkins/dockerhub-webhook/{token}/notify 9 | However, it is possible to disable that requirement with the 10 | system property: 11 |

org.jenkinsci.plugins.registry.notification.webhook.JSONWebHook.DO_NOT_REQUIRE_API_TOKEN=true
12 |

13 |
14 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/token/ApiTokens/resources.css: -------------------------------------------------------------------------------- 1 | .dockerhub-api-token-list .dockerhub-api-token-list-item-row { 2 | display: flex; 3 | align-items: center; 4 | max-width: 700px; 5 | } 6 | .dockerhub-api-token-list .dockerhub-api-token-list-item-row.dockerhub-api-token-list-existing-api-token { 7 | justify-content: space-between; 8 | } 9 | .dockerhub-api-token-list .dockerhub-api-token-list-item .hidden, .dockerhub-api-token-list .dockerhub-api-token-list-empty-item.hidden { 10 | display: none; 11 | } 12 | 13 | .dockerhub-api-token-list .dockerhub-api-token-revoke-button, .dockerhub-api-token-list .dockerhub-new-api-token-value { 14 | padding: 0 0.5rem; 15 | } 16 | .dockerhub-api-token-list .dockerhub-api-token-warning-message, .dockerhub-api-token-list .dockerhub-api-token-save-button { 17 | margin: 0.5rem 0; 18 | } 19 | .dockerhub-api-token-created { 20 | font-size: smaller; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/acr/ACRWebHook.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.registry.notification.webhook.acr; 2 | 3 | import hudson.Extension; 4 | import net.sf.json.JSONObject; 5 | import org.jenkinsci.plugins.registry.notification.webhook.JSONWebHook; 6 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookPayload; 7 | 8 | /** 9 | * The ACRWebHook handles incoming updates from the Azure Container Registry. The provided payload differs minimally 10 | * from what is transmitted by a standard Docker Registry v2 server, which made this separate implementation necessary. 11 | */ 12 | @Extension 13 | public class ACRWebHook extends JSONWebHook { 14 | /** 15 | * The namespace under Jenkins context path that this Action is bound to. 16 | */ 17 | public static final String URL_NAME = "acr-webhook"; 18 | 19 | @Override 20 | protected WebHookPayload createPushNotification(JSONObject payload) { 21 | return new ACRWebHookPayload(payload); 22 | } 23 | 24 | public String getUrlName() { 25 | return URL_NAME; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/resources/private-registry-payload-1-repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "id": "9d9e2ab1-d38e-4f71-8091-2ed699e7a6a5", 5 | "timestamp": "2015-10-06T13:00:54.450615408Z", 6 | "action": "push", 7 | "target": { 8 | "mediaType": "application/octet-stream", 9 | "size": 17680408, 10 | "digest": "sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720", 11 | "length": 17680408, 12 | "repository": "jplock/zookeeper", 13 | "url": "http://registry:5000/v2/jplock/zookeeper/manifests/sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720" 14 | }, 15 | "request": { 16 | "id": "d5af37fb-e766-43a1-81a2-ff6270d96ac8", 17 | "addr": "172.17.42.1:48845", 18 | "host": "registry:5000", 19 | "method": "PUT", 20 | "useragent": "docker/1.7.0 go/go1.4.2 git-commit/0baf609 kernel/4.0.5-boot2docker os/linux arch/amd64" 21 | }, 22 | "actor": {}, 23 | "source": { 24 | "addr": "5dc36776cd85:5000", 25 | "instanceID": "7ca5f1ec-f184-4c47-b4ce-4ad7090dc25d" 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015, CloudBees, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | #TODO 2 | 3 | ##Auto Hook 4 | Currently the [Docker Hub API](https://docs.docker.com/docker-hub/repos/#webhooks) doesn't support adding web hooks. 5 | When support for that is added this plugin should be updated to *automagically* add the hook when configured. 6 | 7 | ##Hook origin Security 8 | The plugin currently blindly accept a wel formed HTTP POST to the end point that contains expected data. 9 | Some measures should be taken to be a bit more secure. Two examples that could both be implemented. 10 | 11 | ###Origin IP check 12 | The [Docker Hub API](https://docs.docker.com/docker-hub/repos/#webhooks) documentation mentions an IP range that all their hooks are coming from. 13 | Implement an IP range check that ignores HTTP POSTs from clients not in that range. 14 | The ranges should be configurable by an admin in case of for example internal proxies. 15 | Work has been started on the `from-trusted-ip` branch. 16 | 17 | ###Poll Docker Hub when a hook arrives to verify that something has actually happened. 18 | This could be tricky due to the CDN that Docker Hub is using. At least from a user's perspective 19 | information isn't updated in the UI until minutes after the actual push and web hook post. -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/index.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2015, CloudBees, Inc. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | 25 | title=Build results for push of {0} to Docker Hub at {1} -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/main.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2015, CloudBees, Inc. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | 25 | heading=Build results for push of {0} to Docker Hub at {1} -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/TriggerListViewColumn/columnHeader.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2015, CloudBees, Inc. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | 25 | tooltip=Docker Hub Notification: Triggered on image names. -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | csanchez/jenkins-swarm-slave 19 | 20 | 21 | 22 | 23 | 24 | false 25 | 26 | 27 | env | sort | grep DOCKER 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/NoResultPage/main.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2015, CloudBees, Inc. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | 25 | blurb=Could not find the build results, it could be because Jenkins has removed all build records. -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/TriggerListViewColumn/help-showMax.html: -------------------------------------------------------------------------------- 1 | 26 |

The max ammount of image names to list. A value of < 0 will show all.

-------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 | Integrates Jenkins with DockerHub and Docker Registry 29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/opt/impl/TriggerForAllUsedInJob/help.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2015, CloudBees, Inc. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | 25 | blurb=Trigger the job based on repositories used by any compatible docker plugin in this job. Currently installed compatible plugins are: -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/TriggerListViewColumn/columnHeader.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.TriggerListViewColumn 25 | 26 | th(tooltip: _("tooltip"), my.columnCaption) -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/TriggerViewFilter/help.html: -------------------------------------------------------------------------------- 1 | 26 |

27 | Show jobs that has a Run when a new image is pushed to Docker Hub trigger configured. 28 |

-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/NoResultPage/main.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.ResultPage.NoResultPage 25 | 26 | h1(_("No result found")) 27 | p(_("blurb")) -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/DockerPullImageBuilder/help-image.html: -------------------------------------------------------------------------------- 1 | 26 |
27 | Image ID on DockerHub. When set in combination with DockerHub trigger, a build will run when dockerhub 28 | builds a new image. 29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/TriggerListViewColumn/config.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.TriggerListViewColumn 25 | 26 | def f = namespace(lib.FormTagLib) 27 | 28 | f.entry(title: "Max", field: "showMax") { 29 | f.textbox(type: "number", name: "showMax") 30 | } -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/TriggerViewFilter/config.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.TriggerViewFilter 25 | 26 | def f = namespace(lib.FormTagLib) 27 | 28 | f.entry(title: _("Patterns")) { 29 | f.expandableTextbox(name: "patterns", field: "patterns", value: instance?.patterns?.join("\n")) 30 | } -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/TriggerViewFilter/help-patterns.html: -------------------------------------------------------------------------------- 1 | 26 |

27 | You can specify a list of 28 | Regular expressions 29 | to match on the image names. One pattern per line. 30 |

31 |

32 | Leave blank to match any image name. 33 |

-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/TriggerListViewColumn/column.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.TriggerListViewColumn 25 | 26 | td { 27 | ul(style: "list-style: none; padding-left: 0; margin: 0") { 28 | my.getImageNames(job).each { String name -> 29 | li { 30 | text(name) 31 | } 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/opt/impl/TriggerOnSpecifiedImageNames/config.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.opt.impl.TriggerOnSpecifiedImageNames 25 | 26 | def st = namespace("jelly:stapler") 27 | def f = namespace(lib.FormTagLib) 28 | 29 | f.entry(title:_("Repositories"), field: "repoNames") { 30 | f.expandableTextbox(value: instance?.repoNames?.join("\n")) 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/DockerPullImageBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/CallbackHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook; 25 | 26 | import hudson.model.Run; 27 | 28 | import java.io.IOException; 29 | import java.util.concurrent.ExecutionException; 30 | 31 | public interface CallbackHandler { 32 | void notify(PushNotification pushNotification, Run run) throws InterruptedException, ExecutionException, IOException; 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/OneConfigured/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | rsandell/test 20 | 21 | 22 | 23 | 24 | 25 | false 26 | 27 | 28 | rsandell/test 29 | 30 | http://hub.rsandell.com 31 | 32 | 33 | 34 | env | sort 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/opt/impl/TriggerForAllUsedInJob/help.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.opt.impl.TriggerForAllUsedInJob 25 | 26 | import hudson.ExtensionList 27 | import jenkins.model.Jenkins 28 | import org.jenkinsci.plugins.docker.commons.DockerImageExtractor 29 | 30 | p(_("blurb")) 31 | ul { 32 | ExtensionList.lookup(DockerImageExtractor).collect{return Jenkins.instance.pluginManager.whichPlugin(it.class)}.toSet().each { 33 | if (it != null) { 34 | li(it.getDisplayName()) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/DockerHubTrigger/help.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2015, CloudBees, Inc. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | 25 | generalBlurb=The job will get triggered when Docker Hub/Registry/ACR notifies about Docker image(s) used in this job has been rebuilt. 26 | details=The Docker Hub does not yet support adding web hooks via the API, so you will need to configure that manually;\ 27 | In your Docker Hub repository, you can find the "webhooks" section and point it to your jenkins instance,\ 28 | set it to one of the below endpoints. 29 | dockerHubUrlBlurb=On Docker Hub 30 | dockerRegistryUrlBlurb=On Docker Registry 31 | acrUrlBlurb=On Azure Container Registry -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/opt/TriggerOption.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.opt; 25 | 26 | import edu.umd.cs.findbugs.annotations.NonNull; 27 | import hudson.model.AbstractDescribableImpl; 28 | import hudson.model.Job; 29 | 30 | import java.util.Collection; 31 | 32 | /** 33 | * A base option in the trigger config. 34 | * 35 | * @author Robert Sandell <rsandell@cloudbees.com>. 36 | */ 37 | public abstract class TriggerOption extends AbstractDescribableImpl { 38 | @NonNull 39 | public abstract Collection getRepoNames(Job job); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/opt/TriggerOptionDescriptor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.opt; 25 | 26 | import hudson.ExtensionList; 27 | import hudson.model.Descriptor; 28 | 29 | import java.util.List; 30 | 31 | /** 32 | * The descriptor of {@link TriggerOption}. 33 | * 34 | * @author Robert Sandell <rsandell@cloudbees.com>. 35 | */ 36 | public abstract class TriggerOptionDescriptor extends Descriptor { 37 | 38 | public static List all() { 39 | return ExtensionList.lookup(TriggerOptionDescriptor.class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/WebHookCause.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook; 25 | 26 | import edu.umd.cs.findbugs.annotations.NonNull; 27 | import hudson.model.Cause; 28 | 29 | public abstract class WebHookCause extends Cause { 30 | @NonNull 31 | protected PushNotification pushNotification; 32 | 33 | public WebHookCause(@NonNull PushNotification pushNotification) { 34 | this.pushNotification = pushNotification; 35 | } 36 | 37 | @NonNull 38 | public PushNotification getPushNotification() { 39 | return pushNotification; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/Messages.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2015, CloudBees, Inc. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | 25 | DockerHubTrigger.DisplayName=Monitor Docker Hub/Registry for image changes 26 | DockerPullImageBuilder.DisplayName=Pull Docker image from Docker Hub/Registry 27 | ResultPage.DisplayName=Results for {0} 28 | TriggerViewFilter_DisplayName=Triggered by push to Docker Hub/Registry 29 | TriggerViewFilter_CompileError=Patterns could not be compiled 30 | TriggerListViewColumn_DisplayName=Docker Hub image names being triggered on 31 | TriggerListViewColumn.ColumnCaption=DHT 32 | 33 | TriggerOption.TriggerForAllUsedInJob.DisplayName=Any referenced Docker image can trigger this job 34 | TriggerOption.TriggerOnSpecifiedImageNames.DisplayName=Specified repositories will trigger this job -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/index.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.ResultPage 25 | 26 | import org.jenkinsci.plugins.registry.notification.TriggerStore 27 | import jenkins.model.Jenkins 28 | 29 | def l = namespace(lib.LayoutTagLib) 30 | def st = namespace("jelly:stapler") 31 | 32 | TriggerStore.TriggerEntry data = my.data 33 | String repoName = data != null ? data.pushNotification.repoName : "" 34 | Date timestamp = data?.pushNotification?.pushedAt; 35 | 36 | 37 | l.layout(title: _("title", repoName, timestamp), norefresh: true, permission: Jenkins.READ) { 38 | include(Jenkins.instance, "sidepanel.jelly") 39 | l."main-panel" { 40 | set("repoName", repoName) 41 | set("triggerData", data) 42 | include(my, "main.groovy") 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/DockerHubTrigger/config.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.DockerHubTrigger 25 | 26 | import org.jenkinsci.plugins.registry.notification.DockerHubTrigger 27 | import org.jenkinsci.plugins.registry.notification.opt.TriggerOptionDescriptor 28 | import org.jenkinsci.plugins.registry.notification.opt.impl.TriggerForAllUsedInJob 29 | 30 | def st = namespace("jelly:stapler") 31 | def f = namespace(lib.FormTagLib) 32 | 33 | def defaultOption = [:] 34 | defaultOption[TriggerForAllUsedInJob.DescriptorImpl.instance] = new TriggerForAllUsedInJob() 35 | 36 | DockerHubTrigger trigger = instance; 37 | 38 | f.descriptorList(title: null, 39 | field: "options", 40 | descriptors: TriggerOptionDescriptor.all(), 41 | instances: trigger != null ? trigger.optionsList : defaultOption) 42 | -------------------------------------------------------------------------------- /src/test/resources/private-registry-payload-2-repositories.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "id": "9d9e2ab1-d38e-4f71-8091-2ed699e7a6a5", 5 | "timestamp": "2015-10-06T13:00:54.450615408Z", 6 | "action": "push", 7 | "target": { 8 | "mediaType": "application/octet-stream", 9 | "size": 17680408, 10 | "digest": "sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720", 11 | "length": 17680408, 12 | "repository": "jplock/zookeeper", 13 | "url": "http://registry:5000/v2/jplock/zookeeper/manifests/sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720" 14 | }, 15 | "request": { 16 | "id": "d5af37fb-e766-43a1-81a2-ff6270d96ac8", 17 | "addr": "172.17.42.1:48845", 18 | "host": "registry:5000", 19 | "method": "PUT", 20 | "useragent": "docker/1.7.0 go/go1.4.2 git-commit/0baf609 kernel/4.0.5-boot2docker os/linux arch/amd64" 21 | }, 22 | "actor": {}, 23 | "source": { 24 | "addr": "5dc36776cd85:5000", 25 | "instanceID": "7ca5f1ec-f184-4c47-b4ce-4ad7090dc25d" 26 | } 27 | }, 28 | { 29 | "id": "154beacd-cd29-4989-bc5e-1a353a776337", 30 | "timestamp": "2015-10-09T13:25:04.102522145Z", 31 | "action": "push", 32 | "target": { 33 | "mediaType": "application/octet-stream", 34 | "size": 71478, 35 | "digest": "sha256:2dbe4abf311d67ec224f926d4b21b9be6f96dfba9be139e6936156a67b75a1ad", 36 | "length": 71478, 37 | "repository": "ubuntu", 38 | "url": "http://registry:5000/v2/ubuntu/manifests/sha256:2dbe4abf311d67ec224f926d4b21b9be6f96dfba9be139e6936156a67b75a1ad" 39 | }, 40 | "request": { 41 | "id": "bd07c479-6ad8-4d80-b9cc-aaf9952e39d8", 42 | "addr": "172.17.42.1:41179", 43 | "host": "registry:5000", 44 | "method": "PUT", 45 | "useragent": "docker/1.7.0 go/go1.4.2 git-commit/0baf609 kernel/4.0.5-boot2docker os/linux arch/amd64" 46 | }, 47 | "actor": {}, 48 | "source": { 49 | "addr": "8c112cfc3fa7:5000", 50 | "instanceID": "a2e89cfe-90e3-4087-8bd5-c0ee89f5ce29" 51 | } 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/DockerHubTrigger/help.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.DockerHubTrigger 25 | 26 | import jenkins.model.Jenkins 27 | 28 | def webHookUrl(String rootAction) { 29 | String rootUrl = Jenkins.instance?.getRootUrl() ?: "http://myJENKINS/"; 30 | return rootUrl + "${rootAction}/{api-token}/notify" 31 | } 32 | 33 | p(_("generalBlurb")) 34 | p(_("details")) 35 | ul { 36 | li { 37 | em { 38 | strong(webHookUrl('dockerhub-webhook')) 39 | raw(' ') 40 | span(_('dockerHubUrlBlurb')) 41 | } 42 | } 43 | li { 44 | em { 45 | strong(webHookUrl('dockerregistry-webhook')) 46 | raw(' ') 47 | span(_('dockerRegistryUrlBlurb')) 48 | } 49 | } 50 | li { 51 | em { 52 | strong(webHookUrl('acr-webhook')) 53 | raw(' ') 54 | span(_('acrUrlBlurb')) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/resources/private-registry-payload-blob-1-repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "id": "9d9e2ab1-d38e-4f71-8091-2ed699e7a6a5", 5 | "timestamp": "2015-10-06T13:00:54.450615408Z", 6 | "action": "push", 7 | "target": { 8 | "mediaType": "application/octet-stream", 9 | "size": 17680408, 10 | "digest": "sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720", 11 | "length": 17680408, 12 | "repository": "jplock/zookeeper", 13 | "url": "http://registry:5000/v2/jplock/zookeeper/blob/sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720" 14 | }, 15 | "request": { 16 | "id": "d5af37fb-e766-43a1-81a2-ff6270d96ac8", 17 | "addr": "172.17.42.1:48845", 18 | "host": "registry:5000", 19 | "method": "PUT", 20 | "useragent": "docker/1.7.0 go/go1.4.2 git-commit/0baf609 kernel/4.0.5-boot2docker os/linux arch/amd64" 21 | }, 22 | "actor": {}, 23 | "source": { 24 | "addr": "5dc36776cd85:5000", 25 | "instanceID": "7ca5f1ec-f184-4c47-b4ce-4ad7090dc25d" 26 | } 27 | }, 28 | { 29 | "id": "9d9e2ab1-d38e-4f71-8091-2ed699e7a6a5", 30 | "timestamp": "2015-10-06T13:00:54.450615408Z", 31 | "action": "push", 32 | "target": { 33 | "mediaType": "application/octet-stream", 34 | "size": 17680408, 35 | "digest": "sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720", 36 | "length": 17680408, 37 | "repository": "jplock/zookeeper", 38 | "url": "http://registry:5000/v2/jplock/zookeeper/manifests/sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720" 39 | }, 40 | "request": { 41 | "id": "d5af37fb-e766-43a1-81a2-ff6270d96ac8", 42 | "addr": "172.17.42.1:48845", 43 | "host": "registry:5000", 44 | "method": "PUT", 45 | "useragent": "docker/1.7.0 go/go1.4.2 git-commit/0baf609 kernel/4.0.5-boot2docker os/linux arch/amd64" 46 | }, 47 | "actor": {}, 48 | "source": { 49 | "addr": "5dc36776cd85:5000", 50 | "instanceID": "7ca5f1ec-f184-4c47-b4ce-4ad7090dc25d" 51 | } 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/test/resources/private-registry-payload-pull-1-repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "id": "9d9e2ab1-d38e-4f71-8091-2ed699e7a6a5", 5 | "timestamp": "2015-10-06T13:00:54.450615408Z", 6 | "action": "pull", 7 | "target": { 8 | "mediaType": "application/octet-stream", 9 | "size": 17680408, 10 | "digest": "sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720", 11 | "length": 17680408, 12 | "repository": "jplock/zookeeper", 13 | "url": "http://registry:5000/v2/jplock/zookeeper/manifests/sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720" 14 | }, 15 | "request": { 16 | "id": "d5af37fb-e766-43a1-81a2-ff6270d96ac8", 17 | "addr": "172.17.42.1:48845", 18 | "host": "registry:5000", 19 | "method": "PUT", 20 | "useragent": "docker/1.7.0 go/go1.4.2 git-commit/0baf609 kernel/4.0.5-boot2docker os/linux arch/amd64" 21 | }, 22 | "actor": {}, 23 | "source": { 24 | "addr": "5dc36776cd85:5000", 25 | "instanceID": "7ca5f1ec-f184-4c47-b4ce-4ad7090dc25d" 26 | } 27 | }, 28 | { 29 | "id": "9d9e2ab1-d38e-4f71-8091-2ed699e7a6a5", 30 | "timestamp": "2015-10-06T13:00:54.450615408Z", 31 | "action": "push", 32 | "target": { 33 | "mediaType": "application/octet-stream", 34 | "size": 17680408, 35 | "digest": "sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720", 36 | "length": 17680408, 37 | "repository": "jplock/zookeeper", 38 | "url": "http://registry:5000/v2/jplock/zookeeper/manifests/sha256:55d2be3e073109b6dfa68aa6ffbce9f5441ab8dd451834eaf0e81874a0b1b720" 39 | }, 40 | "request": { 41 | "id": "d5af37fb-e766-43a1-81a2-ff6270d96ac8", 42 | "addr": "172.17.42.1:48845", 43 | "host": "registry:5000", 44 | "method": "PUT", 45 | "useragent": "docker/1.7.0 go/go1.4.2 git-commit/0baf609 kernel/4.0.5-boot2docker os/linux arch/amd64" 46 | }, 47 | "actor": {}, 48 | "source": { 49 | "addr": "5dc36776cd85:5000", 50 | "instanceID": "7ca5f1ec-f184-4c47-b4ce-4ad7090dc25d" 51 | } 52 | } 53 | 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/acr/ACRWebHookPayload.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.registry.notification.webhook.acr; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import net.sf.json.JSONObject; 5 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookPayload; 6 | import org.joda.time.format.DateTimeFormatter; 7 | import org.joda.time.format.ISODateTimeFormat; 8 | 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | 12 | /** 13 | * Partially implements https://docs.microsoft.com/en-us/azure/container-registry/container-registry-webhook-reference. 14 | */ 15 | public class ACRWebHookPayload extends WebHookPayload { 16 | private static final Logger logger = Logger.getLogger(ACRWebHookPayload.class.getName()); 17 | 18 | public enum Action { 19 | PUSH("push"); 20 | private String name; 21 | 22 | private Action(String name) { 23 | this.name = name; 24 | } 25 | 26 | public String getName() { 27 | return this.name; 28 | } 29 | } 30 | 31 | public ACRWebHookPayload(@NonNull final JSONObject data) { 32 | setData(data); 33 | setJson(data.toString()); 34 | 35 | if (Action.PUSH.getName().equals(data.optString("action"))) { 36 | final JSONObject event = data; 37 | final String host = event.getJSONObject("request").getString("host"); 38 | final String url = new StringBuilder() 39 | .append(host).append("/").append(event.getJSONObject("target").getString("repository")) 40 | .toString(); 41 | if (logger.isLoggable(Level.FINE)) { 42 | logger.log(Level.FINE, "Creating push notification for " + url); 43 | } 44 | pushNotifications.add(new ACRPushNotification(this, url){{ 45 | DateTimeFormatter parser = ISODateTimeFormat.dateTimeParser(); 46 | setPushedAt(parser.parseDateTime(event.getString("timestamp")).toDate()); 47 | setRegistryHost(host); 48 | }}); 49 | } else { 50 | logger.log(Level.FINER, "Unsupported event received: " + data.toString()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/dockerregistry/DockerRegistryWebHookCause.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, HolidayCheck AG. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.dockerregistry; 25 | 26 | 27 | import edu.umd.cs.findbugs.annotations.NonNull; 28 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookCause; 29 | 30 | /** 31 | * The build cause of {@link DockerRegistryWebHook}. 32 | */ 33 | public class DockerRegistryWebHookCause extends WebHookCause { 34 | 35 | public DockerRegistryWebHookCause(@NonNull DockerRegistryPushNotification dockerRegistryPushNotification) { 36 | super(dockerRegistryPushNotification); 37 | } 38 | 39 | @Override 40 | public String getShortDescription() { 41 | return String.format("Triggered by %s", getPushNotification().getShortDescription()); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "DockerRegistryWebHookCause{" + 47 | "payload=" + getPushNotification().getWebHookPayload() + 48 | '}'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/dockerregistry/DockerRegistryWebHook.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, HolidayCheck AG. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.dockerregistry; 25 | 26 | import hudson.Extension; 27 | import net.sf.json.JSONObject; 28 | import org.jenkinsci.plugins.registry.notification.webhook.JSONWebHook; 29 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookPayload; 30 | 31 | import java.util.logging.Logger; 32 | 33 | /** 34 | * The terminal point for the DockerRegistry web hook. 35 | * See Reference 36 | */ 37 | @Extension 38 | public class DockerRegistryWebHook extends JSONWebHook { 39 | private static final Logger logger = Logger.getLogger(DockerRegistryWebHook.class.getName()); 40 | 41 | /** 42 | * The namespace under Jenkins context path that this Action is bound to. 43 | */ 44 | public static final String URL_NAME = "dockerregistry-webhook"; 45 | 46 | @Override 47 | protected WebHookPayload createPushNotification(JSONObject payload) { 48 | return new DockerRegistryWebHookPayload(payload); 49 | } 50 | 51 | public String getUrlName() { 52 | return URL_NAME; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/dockerhub/DockerHubWebHookCause.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.dockerhub; 25 | 26 | 27 | import edu.umd.cs.findbugs.annotations.NonNull; 28 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookCause; 29 | 30 | /** 31 | * The build cause of {@link DockerHubWebHook}. 32 | */ 33 | public class DockerHubWebHookCause extends WebHookCause { 34 | 35 | public DockerHubWebHookCause(@NonNull DockerHubPushNotification dockerHubPushNotification) { 36 | super(dockerHubPushNotification); 37 | } 38 | 39 | @Override 40 | public String getShortDescription() { 41 | return String.format("Triggered by %s", getPushNotification().getShortDescription()); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "DockerHubWebHookCause{" + 47 | "payload=" + getPushNotification().getWebHookPayload() + 48 | '}'; 49 | } 50 | 51 | private transient DockerHubWebHookPayload payload; 52 | 53 | public Object readResolve() { 54 | if (payload != null) { 55 | this.pushNotification = payload.getPushNotifications().get(0); 56 | } 57 | return this; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/registry/notification/webhook/WebHookPayloadTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook; 25 | 26 | import hudson.util.XStream2; 27 | import net.sf.json.JSONObject; 28 | import org.jenkinsci.plugins.registry.notification.webhook.dockerhub.DockerHubWebHookPayload; 29 | import org.junit.Test; 30 | 31 | import static org.hamcrest.core.IsNot.not; 32 | import static org.hamcrest.core.StringContains.containsString; 33 | import static org.hamcrest.MatcherAssert.assertThat; 34 | import static org.junit.Assert.assertNotNull; 35 | 36 | public class WebHookPayloadTest { 37 | 38 | @Test 39 | public void testSerialization() throws Exception { 40 | JSONObject json = new JSONObject(); 41 | JSONObject repository = new JSONObject(); 42 | repository.put("repo_name", "cb/jenkins"); 43 | json.put("repository", repository); 44 | WebHookPayload obj = new DockerHubWebHookPayload(json); 45 | 46 | XStream2 xs = new XStream2(); 47 | xs.allowTypes(new Class[] {DockerHubWebHookPayload.class}); 48 | String xml = xs.toXML(obj); 49 | assertThat(xml, not(containsString(""))); 50 | 51 | DockerHubWebHookPayload nObj = (DockerHubWebHookPayload)xs.fromXML(xml); 52 | assertNotNull(nObj.getData()); 53 | assertNotNull(nObj.getData().get("repository")); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CloudBees Docker Hub/Registry Notification 2 | ================ 3 | 4 | This plugin provides integration between 5 | * Jenkins and Docker Hub 6 | * Jenkins and Docker Registry 2.0 7 | 8 | It does so by utilizing webhooks to trigger one (or more) Jenkins job(s). 9 | This allows you to implement continuous delivery pipelines based on Docker in Jenkins. 10 | 11 | When Jenkins receives a notification of an updated image that is a web-hook from 12 | Docker Hub, it triggers all jobs that have the Docker Hub 13 | trigger enabled and use the Docker image as part of the build. A 14 | Docker Hub Pull build step is provided to retrieve the latest image from 15 | Hub. 16 | 17 | # Configuring Docker Hub 18 | 19 | On the Jenkins Configure Global Security page add an api key. 20 | 21 | 22 | 23 | Configure your Docker Hub repository with a webhook to your public jenkins instance `http://JENKINS/dockerhub-webhook/{api-key}/notify` 24 | 25 | In your hub.docker.com repository, you can find the "webhooks" section and point it to your jenkins instance: 26 | 27 | 28 | 29 | # Configuring Docker Registry 2.0 30 | 31 | Follow Docker Registry 2.0 [documentation](https://docs.docker.com/registry/notifications/) on how to configure registry so that it would send notifications to `http://JENKINS/dockerregistry-webhook/{api-key}/notify` 32 | 33 | The simplest viable configuration looks like this: 34 | ``` 35 | notifications: 36 | endpoints: 37 | - name: jenkinslistener 38 | url: http://JENKINS/dockerregistry-webhook/{api-key}/notify 39 | timeout: 500ms 40 | threshold: 5 41 | backoff: 1s 42 | ``` 43 | 44 | # Configuring Azure Container Registry 45 | 46 | You can find a detailed guide on how to configure webhooks on ACR on 47 | [docs.microsoft.com](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-webhook). 48 | Use `http://JENKINS/acr-webhook/{api-key}/notify` as a "Service URI". 49 | 50 | 51 | # Examples 52 | 53 | Payloads submitted by the hub: 54 | 55 | * [Payload from your own repository](src/test/resources/own-repository-payload.json). 56 | * [Payload from a public repository](src/test/resources/public-repository-payload.json). 57 | 58 | Payloads submitted by the registry: 59 | 60 | * [Payload from your own registry](/src/test/resources/private-registry-payload-1-repository.json). 61 | 62 | The plugin can be tested with 63 | 64 | ```bash 65 | curl -X POST -H "Content-Type: application/json" http://localhost:8080/jenkins/dockerhub-webhook/{api-key}/notify -d @src/test/resources/public-repository-payload.json 66 | ``` 67 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/ResultPage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook; 25 | 26 | import edu.umd.cs.findbugs.annotations.CheckForNull; 27 | import hudson.model.ModelObject; 28 | import org.jenkinsci.plugins.registry.notification.Messages; 29 | import org.jenkinsci.plugins.registry.notification.TriggerStore; 30 | 31 | /** 32 | * Landing page from Docker Hub when multiple builds where triggered for the same web hook. 33 | * @author Robert Sandell <rsandell@cloudbees.com>. 34 | * 35 | * See main.groovy 36 | */ 37 | public class ResultPage implements ModelObject { 38 | public static final ResultPage NO_RESULT = new NoResultPage(); 39 | 40 | private TriggerStore.TriggerEntry data; 41 | 42 | public ResultPage(TriggerStore.TriggerEntry data) { 43 | this.data = data; 44 | } 45 | 46 | @CheckForNull 47 | public TriggerStore.TriggerEntry getData() { 48 | return data; 49 | } 50 | 51 | @Override 52 | public String getDisplayName() { 53 | String repoName = ""; 54 | if (data != null) { 55 | repoName = data.getPushNotification().getRepoName(); 56 | } 57 | return Messages.ResultPage_DisplayName(repoName); 58 | } 59 | 60 | public static class NoResultPage extends ResultPage { 61 | 62 | public NoResultPage() { 63 | super(null); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/resources/own-repository-payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "push_data": { 3 | "pushed_at": 1429028158, 4 | "images": [ 5 | "imagehash1", 6 | "imagehash2", 7 | "imagehash3" 8 | ], 9 | "pusher": "csanchez" 10 | }, 11 | "callback_url": "https://registry.hub.docker.com/u/csanchez/jenkins-swarm-slave/hook/2g1gggacfij1c4a3jd00bf4fe3c3ec0da/", 12 | "repository": { 13 | "status": "Active", 14 | "description": "", 15 | "is_trusted": true, 16 | "full_description": "# Jenkins swarm slave\n\n[`csanchez/jenkins-swarm-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-swarm-slave/)\n\nA [Jenkins swarm](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin) slave.\n\nFor a container with ssh enabled see\n[`csanchez/jenkins-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-slave/)\n\nFor a container with many build tools installed see\n[`maestrodev/build-agent`](https://registry.hub.docker.com/u/maestrodev/build-agent/)\n\n## Running\n\nTo run a Docker container passing [any parameters](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin#SwarmPlugin-AvailableOptions) to the slave\n\n docker run csanchez/jenkins-swarm-slave -master http://jenkins:8080 -username jenkins -password jenkins -executors 1\n\nLinking to the Jenkins master container there is no need to use `--master`\n\n docker run -d --name jenkins -p 8080:8080 csanchez/jenkins-swarm\n docker run -d --link jenkins:jenkins csanchez/jenkins-swarm-slave -username jenkins -password jenkins -executors 1\n\n\n# Building\n\n docker build -t csanchez/jenkins-swarm-slave .\n", 17 | "repo_url": "https://registry.hub.docker.com/u/csanchez/jenkins-swarm-slave/", 18 | "owner": "csanchez", 19 | "is_official": false, 20 | "is_private": false, 21 | "name": "jenkins-swarm-slave", 22 | "namespace": "csanchez", 23 | "star_count": 2, 24 | "comment_count": 0, 25 | "date_created": 1410991410, 26 | "dockerfile": "FROM java:8u40-b22-jdk\n\nMAINTAINER Carlos Sanchez \n\nENV JENKINS_SWARM_VERSION 1.22\nENV HOME /home/jenkins-slave\n\nRUN useradd -c \"Jenkins Slave user\" -d $HOME -m jenkins-slave\nRUN curl --create-dirs -sSLo /usr/share/jenkins/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar http://maven.jenkins-ci.org/content/repositories/releases/org/jenkins-ci/plugins/swarm-client/$JENKINS_SWARM_VERSION/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar \\\n && chmod 755 /usr/share/jenkins\n\nCOPY jenkins-slave.sh /usr/local/bin/jenkins-slave.sh\n\nUSER jenkins-slave\n\nVOLUME /home/jenkins-slave\n\nENTRYPOINT [\"/usr/local/bin/jenkins-slave.sh\"]\n", 27 | "repo_name": "csanchez/jenkins-swarm-slave" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/EnvContributor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.jenkinsci.plugins.registry.notification; 26 | 27 | import edu.umd.cs.findbugs.annotations.NonNull; 28 | import hudson.EnvVars; 29 | import hudson.Extension; 30 | import hudson.model.EnvironmentContributor; 31 | import hudson.model.ParameterValue; 32 | import hudson.model.Run; 33 | import hudson.model.TaskListener; 34 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookCause; 35 | import org.kohsuke.accmod.Restricted; 36 | import org.kohsuke.accmod.restrictions.NoExternalUse; 37 | 38 | import java.io.IOException; 39 | import java.util.Set; 40 | 41 | /** 42 | * Provides environment variables to builds triggered by the plugin. 43 | * 44 | * @author Robert Sandell <rsandell@cloudbees.com>. 45 | */ 46 | @Extension 47 | @Restricted(NoExternalUse.class) 48 | public class EnvContributor extends EnvironmentContributor { 49 | 50 | @Override 51 | public void buildEnvironmentFor(@NonNull Run r, @NonNull EnvVars envs, @NonNull TaskListener listener) throws IOException, InterruptedException { 52 | WebHookCause cause = (WebHookCause)r.getCause(WebHookCause.class); 53 | if (cause != null) { 54 | Set parameters = cause.getPushNotification().getRunParameters(); 55 | for (ParameterValue parameter : parameters) { 56 | parameter.buildEnvironment(r, envs); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/Http.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook; 25 | 26 | import java.io.IOException; 27 | 28 | import edu.umd.cs.findbugs.annotations.NonNull; 29 | import io.jenkins.plugins.okhttp.api.JenkinsOkHttpClient; 30 | import net.sf.json.JSONObject; 31 | import okhttp3.MediaType; 32 | import okhttp3.OkHttpClient; 33 | import okhttp3.Request; 34 | import okhttp3.RequestBody; 35 | import okhttp3.Response; 36 | 37 | /** 38 | * @author Robert Sandell <rsandell@cloudbees.com>. 39 | */ 40 | public class Http { 41 | 42 | private static final MediaType CONTENT_TYPE_JSON_UTF8_ENCODING = MediaType.get("application/json; charset=utf-8"); 43 | 44 | public static int post(@NonNull final String url, @NonNull final JSONObject data) throws IOException { 45 | final OkHttpClient jenkinsHttpClient = JenkinsOkHttpClient.newClientBuilder(new OkHttpClient()) 46 | .followRedirects(false) 47 | .followSslRedirects(false) 48 | .build(); 49 | 50 | final Request request = new Request.Builder().post( 51 | RequestBody.create(CONTENT_TYPE_JSON_UTF8_ENCODING, data.toString())).url(url).build(); 52 | 53 | Response response = jenkinsHttpClient.newCall(request).execute(); 54 | 55 | return response.code(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | **Newer releases are noted in** [GitHub Releases](https://github.com/jenkinsci/dockerhub-notification-plugin/releases). 4 | 5 | Only noting significant user-visible or major API changes, not internal code cleanups and minor bug fixes. 6 | 7 | ## 2.4.0 (Mar 28, 2019) 8 | 9 | * dockerregistry: add TAG to parameters _([PR #22](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/22))_ 10 | 11 | ## 2.3.0 (Jul 12, 2018) 12 | 13 | * Added support for ACR webhooks. 14 | 15 | ## 2.2.1 (Jan 04, 2018) 16 | 17 | * [JENKINS-47736](https://issues.jenkins-ci.org/browse/JENKINS-47736) - 18 | Stop serializing `JSONObjects` over the Remoting to make the plugin compatible with Jenkins 2.102+ 19 | * More info: [Plugins affected by fix for JEP-200](https://wiki.jenkins.io/display/JENKINS/Plugins+affected+by+fix+for+JEP-200) 20 | 21 | ## 2.2.0 (Jun 15, 2016) 22 | * Expose tag and pusher in the build environment _([PR #15](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/15))_ 23 | * _Dev:_ [JENKINS-35629](https://issues.jenkins-ci.org/browse/JENKINS-35629) Convert to new plugin parent pom _([PR #14](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/14))_ 24 | 25 | ## 2.1.0 (May 30, 2016) 26 | * Fix for [SECURITY-170](https://issues.jenkins-ci.org/browse/SECURITY-170) by changing from adding parameters to the build to adding plain environment variables instead. 27 | _[PR #13](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/13)_ 28 | 29 | ## 2.0.0 (March 24, 2016) 30 | 31 | * [JENKINS-30931](https://issues.jenkins-ci.org/browse/JENKINS-30931) Added improved support for Docker Registry 2.0. 32 | _[PR #5](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/5), 33 | [PR #7](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/7), 34 | [PR #11](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/11)_ 35 | _This resulted in significant refactoring and API changes in the plugin, hence the bump of major version. Data from older versions of the plugin should migrate correctly._ 36 | * Substitute Environment Variables into Image Name. 37 | _[PR #8](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/8)_ 38 | * Added CSRF protection exclusions on the web hooks so that the plugin behaves correctly in a more secured Jenkins. 39 | _[PR #12](https://github.com/jenkinsci/dockerhub-notification-plugin/pull/12)_ 40 | 41 | ## 1.0.2 (June 05, 2015) 42 | 43 | * The list view column is no longer added by default. 44 | 45 | ## 1.0.1 (June 02, 2015) 46 | 47 | * The action was not working correctly in a secured Jenkins environment due to [JENKINS-28688](https://issues.jenkins-ci.org/browse/JENKINS-28688) - thanks to [Shay Erlichmen](https://github.com/erlichmen) 48 | 49 | ## 1.0 (May 26, 2015) 50 | 51 | * first release 52 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/TriggerImageExtractor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification; 25 | 26 | import edu.umd.cs.findbugs.annotations.NonNull; 27 | import hudson.Extension; 28 | import hudson.model.Job; 29 | import jenkins.model.ParameterizedJobMixIn; 30 | import org.jenkinsci.plugins.docker.commons.DockerImageExtractor; 31 | import org.jenkinsci.plugins.registry.notification.opt.TriggerOption; 32 | import org.jenkinsci.plugins.registry.notification.opt.impl.TriggerOnSpecifiedImageNames; 33 | 34 | import java.util.Collection; 35 | import java.util.Collections; 36 | 37 | /** 38 | * Extracts the explicitly stated images used by {@link DockerHubTrigger}. 39 | * 40 | * @author Robert Sandell <rsandell@cloudbees.com>. 41 | * @see DockerHubTrigger#getAllRepoNames() 42 | * @see TriggerOnSpecifiedImageNames 43 | */ 44 | @Extension 45 | public class TriggerImageExtractor extends DockerImageExtractor { 46 | @NonNull 47 | @Override 48 | public Collection getDockerImagesUsedByJob(@NonNull Job job) { 49 | if (job instanceof ParameterizedJobMixIn.ParameterizedJob) { 50 | DockerHubTrigger trigger = DockerHubTrigger.getTrigger((ParameterizedJobMixIn.ParameterizedJob)job); 51 | if (trigger != null) { 52 | for (TriggerOption option : trigger.getOptions()) { 53 | if (option instanceof TriggerOnSpecifiedImageNames) { 54 | return ((TriggerOnSpecifiedImageNames)option).getRepoNames(); 55 | } 56 | } 57 | } 58 | } 59 | return Collections.emptySet(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/WebHookCrumbExclusion.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook; 25 | 26 | import hudson.Extension; 27 | import hudson.security.csrf.CrumbExclusion; 28 | import org.jenkinsci.plugins.registry.notification.webhook.acr.ACRWebHook; 29 | import org.jenkinsci.plugins.registry.notification.webhook.dockerhub.DockerHubWebHook; 30 | import org.jenkinsci.plugins.registry.notification.webhook.dockerregistry.DockerRegistryWebHook; 31 | 32 | import jakarta.servlet.FilterChain; 33 | import jakarta.servlet.ServletException; 34 | import jakarta.servlet.http.HttpServletRequest; 35 | import jakarta.servlet.http.HttpServletResponse; 36 | import java.io.IOException; 37 | 38 | /** 39 | * Excludes {@link DockerHubWebHook} and {@link DockerRegistryWebHook} from having a CSRF protection filter. 40 | * 41 | * @author Robert Sandell <rsandell@cloudbees.com>. 42 | */ 43 | @Extension 44 | public class WebHookCrumbExclusion extends CrumbExclusion { 45 | private static final String REGISTRY_BASE = "/" + DockerRegistryWebHook.URL_NAME + "/"; 46 | private static final String HUB_BASE = "/" + DockerHubWebHook.URL_NAME + "/"; 47 | private static final String ACR_BASE = "/" + ACRWebHook.URL_NAME + "/"; 48 | 49 | @Override 50 | public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 51 | String pathInfo = request.getPathInfo(); 52 | if (pathInfo != null && (pathInfo.startsWith(REGISTRY_BASE) || pathInfo.startsWith(HUB_BASE) || pathInfo.startsWith(ACR_BASE))) { 53 | chain.doFilter(request, response); 54 | return true; 55 | } 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/acr/ACRPushNotification.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.registry.notification.webhook.acr; 2 | 3 | import edu.umd.cs.findbugs.annotations.CheckForNull; 4 | import hudson.Util; 5 | import hudson.model.Cause; 6 | import hudson.model.ParameterValue; 7 | import hudson.model.StringParameterValue; 8 | import net.sf.json.JSONObject; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.jenkinsci.plugins.registry.notification.webhook.PushNotification; 11 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookPayload; 12 | 13 | import java.util.HashSet; 14 | import java.util.Set; 15 | 16 | public class ACRPushNotification extends PushNotification { 17 | public static final String KEY_REPO_NAME = WebHookPayload.PREFIX + "REPO_NAME"; 18 | public static final String KEY_DOCKER_REGISTRY_HOST = WebHookPayload.PREFIX + "DOCKER_REGISTRY_HOST"; 19 | public static final String KEY_TAG = WebHookPayload.PREFIX + "TAG"; 20 | 21 | private String registryHost; 22 | private String tag; 23 | 24 | public ACRPushNotification(ACRWebHookPayload webHookPayload, String repoName) { 25 | super(webHookPayload); 26 | if (webHookPayload != null) { 27 | JSONObject data = webHookPayload.getData(); 28 | if (data != null) { 29 | JSONObject target = data.getJSONObject("target"); 30 | if (target != null) { 31 | this.tag = target.optString("tag"); 32 | } 33 | } 34 | } 35 | this.repoName = repoName; 36 | } 37 | 38 | @CheckForNull 39 | public String getRegistryHost() { 40 | return registryHost; 41 | } 42 | 43 | public void setRegistryHost(String registryHost) { 44 | this.registryHost = registryHost; 45 | } 46 | 47 | public String getTag() { 48 | return this.tag; 49 | } 50 | 51 | @Override 52 | public Cause getCause() { 53 | return new ACRWebHookCause(this); 54 | } 55 | 56 | @Override 57 | public Set getRunParameters() { 58 | Set parameters = new HashSet(); 59 | parameters.add(new StringParameterValue(KEY_REPO_NAME, getRepoName())); 60 | parameters.add(new StringParameterValue(KEY_TAG, getTag())); 61 | String host = getRegistryHost(); 62 | if (!StringUtils.isBlank(host)) { 63 | parameters.add(new StringParameterValue(KEY_DOCKER_REGISTRY_HOST, host)); 64 | } 65 | return parameters; 66 | } 67 | 68 | @Override 69 | public String getCauseMessage() { 70 | return "Docker image " + getRepoName() + " has been rebuilt by ACR@" + getRegistryHost(); 71 | } 72 | 73 | public String sha() { 74 | return Util.getDigestOf("acrNotification:" + repoName + Long.toBinaryString(getReceived())); 75 | } 76 | 77 | @Override 78 | public String getShortDescription() { 79 | return String.format("push of %s to ACR@%s", getRepoName(), getRegistryHost()); 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/opt/impl/TriggerForAllUsedInJob.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.opt.impl; 25 | 26 | import edu.umd.cs.findbugs.annotations.CheckForNull; 27 | import hudson.Extension; 28 | import hudson.model.Job; 29 | import jenkins.model.Jenkins; 30 | import org.jenkinsci.plugins.docker.commons.DockerImageExtractor; 31 | import org.jenkinsci.plugins.registry.notification.Messages; 32 | import org.jenkinsci.plugins.registry.notification.opt.TriggerOption; 33 | import org.jenkinsci.plugins.registry.notification.opt.TriggerOptionDescriptor; 34 | import org.kohsuke.stapler.DataBoundConstructor; 35 | 36 | import java.util.Collection; 37 | import java.util.Collections; 38 | 39 | /** 40 | * {@link TriggerOption} to trigger on all images reported by all {@link DockerImageExtractor}s for the job. 41 | * 42 | * @author Robert Sandell <rsandell@cloudbees.com>. 43 | */ 44 | public class TriggerForAllUsedInJob extends TriggerOption { 45 | 46 | @DataBoundConstructor 47 | public TriggerForAllUsedInJob() { 48 | } 49 | 50 | @Override 51 | public Collection getRepoNames(Job job) { 52 | if (job == null) { 53 | // DockerImageExtractor.getDockerImagesUsedByJobFromAll expects a non-null job argument 54 | // Return an empty list if the job argument is null 55 | return Collections.emptyList(); 56 | } 57 | return DockerImageExtractor.getDockerImagesUsedByJobFromAll(job); 58 | } 59 | 60 | @Extension 61 | public static class DescriptorImpl extends TriggerOptionDescriptor { 62 | @Override 63 | public String getDisplayName() { 64 | return Messages.TriggerOption_TriggerForAllUsedInJob_DisplayName(); 65 | } 66 | 67 | @CheckForNull 68 | public static DescriptorImpl getInstance() { 69 | Jenkins jenkins = Jenkins.getInstance(); 70 | if (jenkins != null) { 71 | return jenkins.getDescriptorByType(DescriptorImpl.class); 72 | } 73 | return null; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/main.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.ResultPage 25 | 26 | import org.jenkinsci.plugins.registry.notification.TriggerStore 27 | import hudson.model.Item 28 | import hudson.model.Run 29 | 30 | import java.text.DateFormat 31 | 32 | def l = namespace(lib.LayoutTagLib) 33 | 34 | TriggerStore.TriggerEntry data = triggerData 35 | 36 | link(rel: "stylesheet", href: "${rootURL}/${res(my, "style.css")}", type: "text/css") 37 | 38 | h1(_("heading", repoName, data.pushNotification.pushedAt)) 39 | 40 | data.entries.each { entry -> 41 | Run run = entry.run 42 | if (run != null && (run.hasPermission(Item.READ) || run.hasPermission(Item.DISCOVER))) { 43 | div(class: "build-entry result-${run.result.toString()} ${run.result.color.iconClassName}") { 44 | a(href: "${rootURL}/${run.url}", class: "result-icon") { 45 | l.icon(class: "${run.result.color.iconClassName} icon-lg", alt: run.result.color.description, 46 | tooltip: run.result.color.description) 47 | } 48 | div(class: "build-link") { 49 | a(href: "${rootURL}/${run.url}", class: "model-link", run.getFullDisplayName()) 50 | } 51 | if (run.hasPermission(Item.READ)) { 52 | ul(class: "build-details") { 53 | li(DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, request2.getLocale()).format(run.time)) 54 | li(run.durationString) 55 | li(run.result.toString()) 56 | } 57 | ul(class: "causes") { 58 | run.causes.each { cause -> 59 | li(cause.getShortDescription()) 60 | } 61 | } 62 | div(class: "buttons") { 63 | a(class: "console", href: "${rootURL}/${run.url}console", _("Console")) 64 | run.getArtifactsUpTo(2).each { artifact -> 65 | a(class: "artifact", href: "${rootURL}/${run.url}artifact/${artifact.href}", artifact.displayPath) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/webhook/ResultPage/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | .build-entry { 25 | margin: 0 0 20px; 26 | padding:20px 20px 20px 168px; 27 | border:1px solid #ccc; 28 | position:relative; 29 | max-width:600px; 30 | min-height:168px; 31 | background:#fff; 32 | } 33 | 34 | .build-entry .build-link {font-size:150%; font-weight:bold; margin-bottom:10px;} 35 | body .build-entry > div.build-link > a{ text-decoration:none; background:none !important} 36 | .build-entry .build-link a:hover {box-shadow:none; text-decoration:underline;} 37 | .build-entry .result-icon{ 38 | content:' '; 39 | position:absolute; 40 | border:1px solid; 41 | height:128px; width:128px; top:20px; left:20px; 42 | border-radius:3px; 43 | background:#fff url(docker_128_grey.png) no-repeat center; 44 | } 45 | .build-entry .result-icon > img {position:absolute; top:5px; left:5px;} 46 | .build-entry > ul {margin:0 0 10px; padding:0; list-style: none;} 47 | 48 | 49 | .build-entry ul.build-details li { 50 | display: inline-block; 51 | margin-right: 40px; 52 | } 53 | .build-entry a:hover {box-shadow:inset 0 1px 5px rgba(0,0,0,.3)} 54 | .build-entry .buttons {padding-top:10px} 55 | .build-entry .buttons > a { 56 | border: 1px solid black; 57 | margin-right:10px; 58 | padding: 5px 20px; 59 | border:1px solid #369; 60 | display:inline-block; 61 | border-radius:3px; 62 | text-decoration:none; 63 | color:#000; 64 | background:#eee; 65 | line-height:20px; 66 | } 67 | .build-entry .buttons > a.console:before{content:' '; height:24px; width:24px; margin:-2px 8px -2px -10px; float:left; display:inline-block; background:url(../../../../../../../../../../images/24x24/terminal.png) no-repeat center;} 68 | .build-entry .buttons > a.console:after {content:'...'} 69 | .build-entry.result-SUCCESS .result-icon {background-image:url(docker_128.png)} 70 | .build-entry.result-FAILURE {background:rgba(200,0,0,.1); border-color:#900; color:#600} 71 | .build-entry.result-FAILURE a {color:#900 !important; border-color:#900 !important; background-color:rgba(200,0,0,.1);} 72 | .build-entry.result-UNSTABLE {background:rgba(200,180,0,.1); border-color:#970; color:#640} 73 | .build-entry.result-UNSTABLE a {color:#970 !important; border-color:#970 !important; background-color:rgba(200,180,0,.1);} 74 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/TriggerListViewColumn.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification; 25 | 26 | import hudson.Extension; 27 | import hudson.model.TopLevelItem; 28 | import hudson.views.ListViewColumn; 29 | import hudson.views.ListViewColumnDescriptor; 30 | import jenkins.model.ParameterizedJobMixIn; 31 | import org.jenkinsci.Symbol; 32 | import org.kohsuke.stapler.DataBoundConstructor; 33 | import org.kohsuke.stapler.DataBoundSetter; 34 | 35 | import java.util.Arrays; 36 | import java.util.Collection; 37 | import java.util.Collections; 38 | import java.util.Set; 39 | 40 | /** 41 | * Lists the images that a job triggers on. 42 | * 43 | * @author Robert Sandell <rsandell@cloudbees.com>. 44 | */ 45 | public class TriggerListViewColumn extends ListViewColumn { 46 | 47 | private int showMax; 48 | 49 | @DataBoundConstructor 50 | public TriggerListViewColumn() { 51 | showMax = 0; 52 | } 53 | 54 | public int getShowMax() { 55 | return showMax; 56 | } 57 | 58 | @DataBoundSetter 59 | public void setShowMax(int showMax) { 60 | this.showMax = showMax; 61 | } 62 | 63 | @Override 64 | public String getColumnCaption() { 65 | return Messages.TriggerListViewColumn_ColumnCaption(); 66 | } 67 | 68 | public Collection getImageNames(TopLevelItem item) { 69 | if (item instanceof ParameterizedJobMixIn.ParameterizedJob) { 70 | DockerHubTrigger trigger = DockerHubTrigger.getTrigger((ParameterizedJobMixIn.ParameterizedJob)item); 71 | if (trigger != null) { 72 | Set names = trigger.getAllRepoNames(); 73 | if (showMax <= 0 || names.size() <= showMax) { 74 | return names; 75 | } else { 76 | String[] array = names.toArray(new String[names.size()]); 77 | return Arrays.asList(Arrays.copyOf(array, showMax)); 78 | } 79 | } 80 | } 81 | return Collections.emptySet(); 82 | } 83 | 84 | @Extension @Symbol("dockerImageNames") 85 | public static class DescriptorImpl extends ListViewColumnDescriptor { 86 | 87 | @Override 88 | public String getDisplayName() { 89 | return Messages.TriggerListViewColumn_DisplayName(); 90 | } 91 | 92 | @Override 93 | public boolean shownByDefault() { 94 | return false; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/registry/notification/DockerHubTriggerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification; 25 | 26 | import org.jenkinsci.plugins.registry.notification.opt.impl.TriggerOnSpecifiedImageNames; 27 | import org.jenkinsci.plugins.registry.notification.opt.impl.TriggerForAllUsedInJob; 28 | 29 | import hudson.model.Item; 30 | import hudson.model.FreeStyleProject; 31 | 32 | import org.junit.Rule; 33 | import org.junit.Test; 34 | import org.jvnet.hudson.test.JenkinsRule; 35 | 36 | import static org.hamcrest.collection.IsEmptyCollection.empty; 37 | import static org.hamcrest.MatcherAssert.assertThat; 38 | import static org.hamcrest.Matchers.contains; 39 | import static org.junit.Assert.assertNotNull; 40 | 41 | /** 42 | * Tests for {@link DockerHubTrigger}. 43 | */ 44 | public class DockerHubTriggerTest { 45 | 46 | @Rule 47 | public JenkinsRule j = new JenkinsRule(); 48 | 49 | @Test 50 | public void testConfigRoundTrip() throws Exception { 51 | String[] expectedRepoNames = new String[] {"cb/jenkins", "cb/je"}; 52 | FreeStyleProject project = j.createFreeStyleProject(); 53 | project.addTrigger(new DockerHubTrigger(new TriggerForAllUsedInJob(), new TriggerOnSpecifiedImageNames(expectedRepoNames))); 54 | DockerHubTrigger trigger = DockerHubTrigger.getTrigger(project); 55 | assertNotNull(trigger); 56 | assertThat(trigger.getAllRepoNames(), contains(expectedRepoNames)); 57 | project = (FreeStyleProject) j.configRoundtrip((Item)project); 58 | trigger = DockerHubTrigger.getTrigger(project); 59 | assertNotNull(trigger); 60 | assertThat(trigger.getAllRepoNames(), contains(expectedRepoNames)); 61 | assertThat(trigger.getAllRepoNames(), contains(expectedRepoNames)); 62 | } 63 | 64 | @Test 65 | public void testConfigRoundTripEmptyNames() throws Exception { 66 | FreeStyleProject project = j.createFreeStyleProject(); 67 | project.addTrigger(new DockerHubTrigger(new TriggerForAllUsedInJob(), new TriggerOnSpecifiedImageNames())); 68 | DockerHubTrigger trigger = DockerHubTrigger.getTrigger(project); 69 | assertNotNull(trigger); 70 | assertThat(trigger.getAllRepoNames(), empty()); 71 | project = (FreeStyleProject) j.configRoundtrip((Item)project); 72 | trigger = DockerHubTrigger.getTrigger(project); 73 | assertNotNull(trigger); 74 | assertThat(trigger.getAllRepoNames(), empty()); 75 | assertThat(trigger.getAllRepoNames(), empty()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/WebHookPayload.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook; 25 | 26 | import edu.umd.cs.findbugs.annotations.CheckForNull; 27 | import net.sf.json.JSONObject; 28 | 29 | import java.io.Serializable; 30 | import java.util.ArrayList; 31 | import java.util.Collections; 32 | import java.util.List; 33 | 34 | public abstract class WebHookPayload implements Serializable { 35 | 36 | public static final String PREFIX = "DOCKER_TRIGGER_"; 37 | 38 | @CheckForNull 39 | private transient JSONObject data; 40 | @CheckForNull 41 | private String json; 42 | 43 | protected final long received; 44 | 45 | protected List pushNotifications = new ArrayList(); 46 | 47 | public WebHookPayload() { 48 | this.received = System.currentTimeMillis(); 49 | } 50 | 51 | /** 52 | * {@link System#currentTimeMillis()} when this object's constructor was called. 53 | * 54 | * @return the object's creation time/when the payload was received. 55 | */ 56 | public long getReceived() { 57 | return received; 58 | } 59 | 60 | public List getPushNotifications() { 61 | return Collections.unmodifiableList(pushNotifications); 62 | } 63 | 64 | @CheckForNull 65 | public JSONObject getData() { 66 | return data; 67 | } 68 | 69 | protected void setData(JSONObject data) { 70 | this.data = data; 71 | } 72 | 73 | protected void setJson(String json) { 74 | this.json = json; 75 | } 76 | 77 | @Override 78 | public boolean equals(Object o) { 79 | if (this == o) return true; 80 | if (o == null || getClass() != o.getClass()) return false; 81 | 82 | WebHookPayload that = (WebHookPayload) o; 83 | 84 | if (received != that.received) return false; 85 | return !(data != null ? !data.equals(that.data) : that.data != null); 86 | 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | int result = data != null ? data.hashCode() : 0; 92 | result = 31 * result + (int) (received ^ (received >>> 32)); 93 | return result; 94 | } 95 | 96 | protected Object readResolve() { 97 | if (this.data == null && this.json != null) { 98 | this.data = JSONObject.fromObject(this.json); 99 | } 100 | return this; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/dockerhub/DockerHubWebHook.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.dockerhub; 25 | 26 | import hudson.Extension; 27 | import hudson.Main; 28 | import net.sf.json.JSONObject; 29 | import org.jenkinsci.plugins.registry.notification.webhook.JSONWebHook; 30 | import org.jenkinsci.plugins.registry.notification.webhook.PushNotification; 31 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookPayload; 32 | import org.kohsuke.stapler.QueryParameter; 33 | import org.kohsuke.stapler.StaplerResponse2; 34 | 35 | import java.io.IOException; 36 | import java.util.logging.Logger; 37 | 38 | /** 39 | * The terminal point for the DockerHub web hook. 40 | * See Reference 41 | */ 42 | @Extension 43 | public class DockerHubWebHook extends JSONWebHook { 44 | private static final Logger logger = Logger.getLogger(DockerHubWebHook.class.getName()); 45 | 46 | /** 47 | * The namespace under Jenkins context path that this Action is bound to. 48 | */ 49 | public static final String URL_NAME = "dockerhub-webhook"; 50 | 51 | private boolean isDebugMode() { 52 | if (Main.isDevelopmentMode || Main.isUnitTest) { 53 | return true; 54 | } else if (System.getProperty("hudson.hpi.run") != null) { 55 | return true; 56 | } else { 57 | return false; 58 | } 59 | } 60 | 61 | /** 62 | * Helper for development without Dockerub integration. 63 | * 64 | * @param image the docker image to trigger 65 | * @param response to send a redirect to 66 | * @throws IOException if so 67 | */ 68 | public void doDebug(@QueryParameter(required = true) String image, StaplerResponse2 response) throws IOException { 69 | if (!isDebugMode()) { 70 | throw new IllegalStateException("This endpoint can only be used during development!"); 71 | } 72 | DockerHubWebHookPayload dockerHubWebHookPayload = new DockerHubWebHookPayload(image); 73 | for (PushNotification pushNotification : dockerHubWebHookPayload.getPushNotifications()) { 74 | trigger(response, pushNotification); //TODO pre-filled json data when needed. 75 | } 76 | } 77 | 78 | @Override 79 | protected void trigger(StaplerResponse2 response, PushNotification pushNotification) throws IOException { 80 | super.trigger(response, pushNotification); 81 | response.sendRedirect("../"); 82 | } 83 | 84 | @Override 85 | protected WebHookPayload createPushNotification(JSONObject payload) { 86 | return new DockerHubWebHookPayload(payload); 87 | } 88 | 89 | public String getUrlName() { 90 | return URL_NAME; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/registry/notification/JcasCTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification; 25 | 26 | import hudson.model.ListView; 27 | import hudson.model.View; 28 | import io.jenkins.plugins.casc.misc.RoundTripAbstractTest; 29 | import org.junit.runner.RunWith; 30 | import org.junit.runners.Parameterized; 31 | import org.jvnet.hudson.test.RestartableJenkinsRule; 32 | 33 | import static org.hamcrest.MatcherAssert.assertThat; 34 | import static org.hamcrest.Matchers.allOf; 35 | import static org.hamcrest.Matchers.equalTo; 36 | import static org.hamcrest.Matchers.hasItem; 37 | import static org.hamcrest.Matchers.hasProperty; 38 | import static org.hamcrest.Matchers.instanceOf; 39 | import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; 40 | import static org.junit.Assert.assertNotNull; 41 | 42 | /** 43 | * Tests that {@link TriggerViewFilter} and {@link TriggerListViewColumn} can be configured with configuration as code. 44 | * 45 | * Testing both with a configuration before symbols where added and with the new symbols. 46 | * 47 | * @see Configuration as Code Plugin 48 | */ 49 | @RunWith(Parameterized.class) 50 | public class JcasCTest extends RoundTripAbstractTest { 51 | 52 | private String resource; 53 | 54 | public JcasCTest(final String resource) { 55 | this.resource = resource; 56 | } 57 | 58 | @Override 59 | protected void assertConfiguredAsExpected(final RestartableJenkinsRule j, final String s) { 60 | final View view = j.j.jenkins.getView("Docker"); 61 | assertNotNull(view); 62 | ListView dv = (ListView) view; 63 | assertThat(dv.getColumns(), hasItem( 64 | allOf( 65 | instanceOf(TriggerListViewColumn.class), 66 | hasProperty("showMax", equalTo(3)) 67 | ) 68 | )); 69 | assertThat(dv.getJobFilters(), hasItem( 70 | allOf( 71 | instanceOf(TriggerViewFilter.class), 72 | hasProperty("patterns", containsInAnyOrder( 73 | equalTo(".*"), 74 | equalTo("jenkins")) 75 | ) 76 | ) 77 | )); 78 | } 79 | 80 | @Override 81 | protected String stringInLogExpected() { 82 | return "Setting org.jenkinsci.plugins.registry.notification.TriggerListViewColumn"; 83 | } 84 | 85 | @Parameterized.Parameters(name = "{0}") 86 | public static Object[][] params() { 87 | return new Object[][]{{"/jcasc_bare.yaml"}, {"/jcasc_symbols.yaml"}}; 88 | } 89 | 90 | @Override 91 | protected String configResource() { 92 | return resource; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/opt/impl/TriggerOnSpecifiedImageNames.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.opt.impl; 25 | 26 | import hudson.Extension; 27 | import hudson.model.Job; 28 | import net.sf.json.JSONArray; 29 | import net.sf.json.JSONObject; 30 | import org.apache.commons.lang.StringUtils; 31 | import org.jenkinsci.plugins.registry.notification.Messages; 32 | import org.jenkinsci.plugins.registry.notification.opt.TriggerOption; 33 | import org.jenkinsci.plugins.registry.notification.opt.TriggerOptionDescriptor; 34 | import org.kohsuke.stapler.DataBoundConstructor; 35 | import org.kohsuke.stapler.DataBoundSetter; 36 | import org.kohsuke.stapler.StaplerRequest2; 37 | 38 | import java.util.*; 39 | 40 | /** 41 | * {@link TriggerOption} with manually specified names. 42 | * 43 | * @author Robert Sandell <rsandell@cloudbees.com>. 44 | */ 45 | public class TriggerOnSpecifiedImageNames extends TriggerOption { 46 | private Set repoNames; 47 | 48 | @DataBoundConstructor 49 | public TriggerOnSpecifiedImageNames() { 50 | this.repoNames = Collections.emptySet(); 51 | } 52 | 53 | public TriggerOnSpecifiedImageNames(Collection repoNames) { 54 | this.repoNames = new HashSet(); 55 | if (repoNames != null) { 56 | this.repoNames.addAll(repoNames); 57 | } 58 | } 59 | 60 | public TriggerOnSpecifiedImageNames(String... repoNames) { 61 | this(Arrays.asList(repoNames)); 62 | } 63 | 64 | public Set getRepoNames() { 65 | return repoNames; 66 | } 67 | 68 | @DataBoundSetter 69 | public void setRepoNames(Set repoNames) { 70 | this.repoNames = repoNames; 71 | } 72 | 73 | @Override 74 | public Collection getRepoNames(Job job) { 75 | return getRepoNames(); 76 | } 77 | 78 | @Extension 79 | public static class DescriptorImpl extends TriggerOptionDescriptor { 80 | @Override 81 | public String getDisplayName() { 82 | return Messages.TriggerOption_TriggerOnSpecifiedImageNames_DisplayName(); 83 | } 84 | 85 | @Override 86 | public TriggerOption newInstance(StaplerRequest2 req, JSONObject formData) throws FormException { 87 | // TODO JENKINS-27901: need a standard control for this 88 | if (formData.has("repoNames") && !StringUtils.isBlank(formData.optString("repoNames"))) { 89 | JSONArray array = new JSONArray(); 90 | array.addAll(Arrays.asList(StringUtils.split(formData.getString("repoNames")))); 91 | formData.put("repoNames", array); 92 | } else { 93 | formData.put("repoNames", new JSONArray()); 94 | } 95 | return super.newInstance(req, formData); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/registry/notification/ACRWebHookTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.registry.notification; 2 | 3 | import hudson.model.Cause; 4 | import hudson.model.FreeStyleProject; 5 | import hudson.model.Result; 6 | import hudson.model.Run; 7 | import hudson.model.listeners.RunListener; 8 | import net.sf.json.JSONObject; 9 | import org.apache.commons.io.IOUtils; 10 | import org.jenkinsci.plugins.registry.notification.opt.TriggerOption; 11 | import org.jenkinsci.plugins.registry.notification.opt.impl.TriggerOnSpecifiedImageNames; 12 | import org.jenkinsci.plugins.registry.notification.token.ApiTokens; 13 | import org.jenkinsci.plugins.registry.notification.webhook.Http; 14 | import org.jenkinsci.plugins.registry.notification.webhook.acr.ACRPushNotification; 15 | import org.jenkinsci.plugins.registry.notification.webhook.acr.ACRWebHook; 16 | import org.jenkinsci.plugins.registry.notification.webhook.acr.ACRWebHookCause; 17 | import org.junit.Before; 18 | import org.junit.Rule; 19 | import org.junit.Test; 20 | import org.jvnet.hudson.test.JenkinsRule; 21 | import org.jvnet.hudson.test.MockBuilder; 22 | import org.jvnet.hudson.test.TestExtension; 23 | 24 | import java.io.IOException; 25 | import java.util.ArrayList; 26 | import java.util.Collections; 27 | import java.util.List; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertNotNull; 31 | 32 | /** 33 | * Testing ACR webhook. 34 | */ 35 | public class ACRWebHookTest { 36 | @Rule 37 | public JenkinsRule j = new JenkinsRule(); 38 | 39 | private String token; 40 | 41 | private String getWebHookURL() throws IOException { 42 | return this.j.getURL() + ACRWebHook.URL_NAME + "/" + token + "/notify"; 43 | } 44 | 45 | @Before 46 | public void setUp() { 47 | final JSONObject test = ApiTokens.get().generateApiToken("test"); 48 | token = test.getString("value"); 49 | } 50 | 51 | @Test 52 | public void testValidRepoUpdate() throws Exception { 53 | PushNotificationRunListener listener = j.jenkins.getExtensionList(PushNotificationRunListener.class).get(0); 54 | listener.reset(); 55 | createProjectWithTrigger(new TriggerOnSpecifiedImageNames(Collections.singletonList("myregistry.azurecr.io/hello-world"))); 56 | JSONObject data = JSONObject.fromObject(IOUtils.toString(getClass().getResourceAsStream("/acr-payload-valid.json"))); 57 | assertEquals(200, Http.post(getWebHookURL(), data)); 58 | j.waitUntilNoActivity(); 59 | ACRPushNotification notification = listener.getPushNotification(); 60 | assertNotNull(notification); 61 | assertEquals("v1", notification.getTag()); 62 | assertEquals("myregistry.azurecr.io", notification.getRegistryHost()); 63 | 64 | } 65 | 66 | private void createProjectWithTrigger(TriggerOption... options) throws Exception { 67 | FreeStyleProject project = this.j.createFreeStyleProject(); 68 | project.addTrigger(new DockerHubTrigger(options)); 69 | project.getBuildersList().add(new MockBuilder(Result.SUCCESS)); 70 | } 71 | 72 | @TestExtension 73 | public static class PushNotificationRunListener extends RunListener> { 74 | private List hits; 75 | 76 | public PushNotificationRunListener() { 77 | this.hits = new ArrayList(); 78 | } 79 | 80 | @Override 81 | public void onFinalized(Run run) { 82 | super.onFinalized(run); 83 | this.hits.add(run); 84 | } 85 | 86 | public void reset() { 87 | this.hits.clear(); 88 | } 89 | 90 | public ACRPushNotification getPushNotification() { 91 | for (Run hit : this.hits) { 92 | ACRWebHookCause cause = (ACRWebHookCause) hit.getCause(ACRWebHookCause.class); 93 | return (ACRPushNotification) cause.getPushNotification(); 94 | } 95 | return null; 96 | } 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/registry/notification/DockerHubWebHookTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification; 25 | 26 | import hudson.EnvVars; 27 | import hudson.Launcher; 28 | import hudson.model.AbstractBuild; 29 | import hudson.model.BuildListener; 30 | import hudson.model.FreeStyleBuild; 31 | import hudson.tasks.BuildStep; 32 | import hudson.tasks.Builder; 33 | import jenkins.tasks.SimpleBuildStep; 34 | import org.jenkinsci.plugins.registry.notification.opt.impl.TriggerOnSpecifiedImageNames; 35 | import hudson.model.FreeStyleProject; 36 | import hudson.model.Result; 37 | import org.jenkinsci.plugins.registry.notification.webhook.dockerhub.DockerHubPushNotification; 38 | import org.junit.Rule; 39 | import org.junit.Test; 40 | import org.jvnet.hudson.test.JenkinsRule; 41 | import org.jvnet.hudson.test.MockBuilder; 42 | 43 | import java.io.IOException; 44 | import java.util.Map; 45 | 46 | public class DockerHubWebHookTest { 47 | 48 | @Rule 49 | public JenkinsRule j = new JenkinsRule(); 50 | 51 | @Test(timeout = 60000) 52 | public void testDoIndex() throws Exception { 53 | FreeStyleProject project = j.createFreeStyleProject(); 54 | final String repoName = "cb/jenkins"; 55 | project.addTrigger(new DockerHubTrigger(new TriggerOnSpecifiedImageNames(repoName))); 56 | project.getBuildersList().add(new MockBuilder(Result.SUCCESS)); 57 | j.createWebClient().goTo("dockerhub-webhook/debug?image=" + repoName); 58 | 59 | j.waitUntilNoActivity(); 60 | 61 | j.assertLogContains(repoName, project.getLastBuild()); 62 | } 63 | 64 | @Test(timeout = 60000) 65 | public void testEnvironment() throws Exception { 66 | FreeStyleProject project = j.createFreeStyleProject(); 67 | final String repoName = "cb/jenkins"; 68 | project.addTrigger(new DockerHubTrigger(new TriggerOnSpecifiedImageNames(repoName))); 69 | project.getBuildersList().add(new PrintEnvironment()); 70 | project.getBuildersList().add(new MockBuilder(Result.SUCCESS)); 71 | j.createWebClient().goTo("dockerhub-webhook/debug?image=" + repoName); 72 | 73 | j.waitUntilNoActivity(); 74 | FreeStyleBuild build = project.getLastBuild(); 75 | j.assertLogContains(repoName, build); 76 | j.assertLogContains(DockerHubPushNotification.KEY_REPO_NAME + " = " + repoName, build); 77 | } 78 | 79 | static class PrintEnvironment extends Builder { 80 | @Override 81 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { 82 | EnvVars vars = build.getEnvironment(listener); 83 | for (Map.Entry var : vars.entrySet()) { 84 | listener.getLogger().println(var.getKey() + " = " + var.getValue()); 85 | } 86 | return true; 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/fingerprints/58/73/052b0750060b97b428e5dbdd1c03.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2016-03-14 14:53:40.641 UTC 4 | 5873052b0750060b97b428e5dbdd1c03 5 | csanchez/jenkins-swarm-slave 6 | 7 | 8 | 9 | 1457967220599 10 | 11 | csanchez/jenkins-swarm-slave 12 | {"push_data":{"pushed_at":1429028158,"images":["imagehash1","imagehash2","imagehash3"],"pusher":"csanchez"},"callback_url":"https://registry.hub.example.com/u/csanchez/jenkins-swarm-slave/hook/2g1gggacfij1c4a3jd00bf4fe3c3ec0da/","repository":{"status":"Active","description":"","is_trusted":true,"full_description":"# Jenkins swarm slave\n\n[`csanchez/jenkins-swarm-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-swarm-slave/)\n\nA [Jenkins swarm](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin) slave.\n\nFor a container with ssh enabled see\n[`csanchez/jenkins-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-slave/)\n\nFor a container with many build tools installed see\n[`maestrodev/build-agent`](https://registry.hub.docker.com/u/maestrodev/build-agent/)\n\n## Running\n\nTo run a Docker container passing [any parameters](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin#SwarmPlugin-AvailableOptions) to the slave\n\n docker run csanchez/jenkins-swarm-slave -master http://jenkins:8080 -username jenkins -password jenkins -executors 1\n\nLinking to the Jenkins master container there is no need to use `--master`\n\n docker run -d --name jenkins -p 8080:8080 csanchez/jenkins-swarm\n docker run -d --link jenkins:jenkins csanchez/jenkins-swarm-slave -username jenkins -password jenkins -executors 1\n\n\n# Building\n\n docker build -t csanchez/jenkins-swarm-slave .\n","repo_url":"https://registry.hub.example.com/u/csanchez/jenkins-swarm-slave/","owner":"csanchez","is_official":false,"is_private":false,"name":"jenkins-swarm-slave","namespace":"csanchez","star_count":2,"comment_count":0,"date_created":1410991410,"dockerfile":"FROM java:8u40-b22-jdk\n\nMAINTAINER Carlos Sanchez <carlos@apache.org>\n\nENV JENKINS_SWARM_VERSION 1.22\nENV HOME /home/jenkins-slave\n\nRUN useradd -c \"Jenkins Slave user\" -d $HOME -m jenkins-slave\nRUN curl --create-dirs -sSLo /usr/share/jenkins/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar http://maven.jenkins-ci.org/content/repositories/releases/org/jenkins-ci/plugins/swarm-client/$JENKINS_SWARM_VERSION/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar \\\n && chmod 755 /usr/share/jenkins\n\nCOPY jenkins-slave.sh /usr/local/bin/jenkins-slave.sh\n\nUSER jenkins-slave\n\nVOLUME /home/jenkins-slave\n\nENTRYPOINT [\"/usr/local/bin/jenkins-slave.sh\"]\n","repo_name":"csanchez/jenkins-swarm-slave"}} 13 | 1457967220599 14 | 15 | 16 | 17 | JenkinsSlaveTrigger 18 | 1 19 | true 20 | 21 | 22 | 23 | error 24 | Build result FAILURE 25 | Jenkins 26 | dockerhub-webhook/details/5873052b0750060b97b428e5dbdd1c03 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/fingerprints/e9/d6/eb6cd6a7bfcd2bd622765a87893f.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2016-03-14 14:54:57.198 UTC 4 | e9d6eb6cd6a7bfcd2bd622765a87893f 5 | csanchez/jenkins-swarm-slave 6 | 7 | 8 | 9 | 1457967297196 10 | 11 | csanchez/jenkins-swarm-slave 12 | {"push_data":{"pushed_at":1429028158,"images":["imagehash1","imagehash2","imagehash3"],"pusher":"csanchez"},"callback_url":"https://registry.hub.example.com/u/csanchez/jenkins-swarm-slave/hook/2g1gggacfij1c4a3jd00bf4fe3c3ec0da/","repository":{"status":"Active","description":"","is_trusted":true,"full_description":"# Jenkins swarm slave\n\n[`csanchez/jenkins-swarm-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-swarm-slave/)\n\nA [Jenkins swarm](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin) slave.\n\nFor a container with ssh enabled see\n[`csanchez/jenkins-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-slave/)\n\nFor a container with many build tools installed see\n[`maestrodev/build-agent`](https://registry.hub.docker.com/u/maestrodev/build-agent/)\n\n## Running\n\nTo run a Docker container passing [any parameters](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin#SwarmPlugin-AvailableOptions) to the slave\n\n docker run csanchez/jenkins-swarm-slave -master http://jenkins:8080 -username jenkins -password jenkins -executors 1\n\nLinking to the Jenkins master container there is no need to use `--master`\n\n docker run -d --name jenkins -p 8080:8080 csanchez/jenkins-swarm\n docker run -d --link jenkins:jenkins csanchez/jenkins-swarm-slave -username jenkins -password jenkins -executors 1\n\n\n# Building\n\n docker build -t csanchez/jenkins-swarm-slave .\n","repo_url":"https://registry.hub.example.com/u/csanchez/jenkins-swarm-slave/","owner":"csanchez","is_official":false,"is_private":false,"name":"jenkins-swarm-slave","namespace":"csanchez","star_count":2,"comment_count":0,"date_created":1410991410,"dockerfile":"FROM java:8u40-b22-jdk\n\nMAINTAINER Carlos Sanchez <carlos@apache.org>\n\nENV JENKINS_SWARM_VERSION 1.22\nENV HOME /home/jenkins-slave\n\nRUN useradd -c \"Jenkins Slave user\" -d $HOME -m jenkins-slave\nRUN curl --create-dirs -sSLo /usr/share/jenkins/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar http://maven.jenkins-ci.org/content/repositories/releases/org/jenkins-ci/plugins/swarm-client/$JENKINS_SWARM_VERSION/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar \\\n && chmod 755 /usr/share/jenkins\n\nCOPY jenkins-slave.sh /usr/local/bin/jenkins-slave.sh\n\nUSER jenkins-slave\n\nVOLUME /home/jenkins-slave\n\nENTRYPOINT [\"/usr/local/bin/jenkins-slave.sh\"]\n","repo_name":"csanchez/jenkins-swarm-slave"}} 13 | 1457967297196 14 | 15 | 16 | 17 | JenkinsSlaveTrigger 18 | 2 19 | true 20 | 21 | 22 | 23 | success 24 | Build result SUCCESS 25 | Jenkins 26 | dockerhub-webhook/details/e9d6eb6cd6a7bfcd2bd622765a87893f 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/dockerregistry/DockerRegistryWebHookPayload.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, HolidayCheck AG. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.dockerregistry; 25 | 26 | import edu.umd.cs.findbugs.annotations.NonNull; 27 | import net.sf.json.JSONArray; 28 | import net.sf.json.JSONObject; 29 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookPayload; 30 | import org.joda.time.format.DateTimeFormatter; 31 | import org.joda.time.format.ISODateTimeFormat; 32 | 33 | import java.util.logging.Logger; 34 | import java.util.logging.Level; 35 | 36 | public class DockerRegistryWebHookPayload extends WebHookPayload { 37 | 38 | private static final Logger logger = Logger.getLogger(DockerRegistryPushNotification.class.getName()); 39 | 40 | /** 41 | * Creates the object from the json payload 42 | * 43 | * @param data the json payload 44 | * @throws net.sf.json.JSONException if the key {@code repository.repo_name} doesn't exist. 45 | */ 46 | public DockerRegistryWebHookPayload(@NonNull JSONObject data) { 47 | super(); 48 | setData(data); 49 | setJson(data.toString()); 50 | 51 | JSONArray events = data.getJSONArray("events"); 52 | 53 | for (int i = 0, size = events.size(); i < size; i++) { 54 | JSONObject event = events.getJSONObject(i); 55 | String separator = "/"; 56 | 57 | if ( event.optString("action").equals("push") ) { 58 | final String[] urlSegments = event.getJSONObject("target").optString("url").split(separator); 59 | StringBuffer sb = new StringBuffer(); 60 | sb.append(urlSegments[2]); 61 | sb.append(separator); 62 | sb.append(event.getJSONObject("target").optString("repository")); 63 | String repository = sb.toString(); 64 | if (urlSegments[urlSegments.length - 2 ].equals("manifests")) { 65 | pushNotifications.add(createPushNotification(repository, event)); 66 | } else { 67 | logger.log(Level.FINER, "Skipping Layer Push notifications"); 68 | 69 | } 70 | } else { 71 | logger.log(Level.FINER, "Skipping pull notification" + event.getJSONObject("target").optString("repository")); 72 | } 73 | } 74 | 75 | } 76 | 77 | private DockerRegistryPushNotification createPushNotification(@NonNull final String repoName, @NonNull JSONObject data) { 78 | final String timestamp = data.optString("timestamp"); 79 | final String host = data.getJSONObject("request").optString("host"); 80 | final String tag = data.getJSONObject("target").optString("tag"); 81 | return new DockerRegistryPushNotification(this, repoName){{ 82 | DateTimeFormatter parser = ISODateTimeFormat.dateTimeParser(); 83 | setTag(tag); 84 | setPushedAt(parser.parseDateTime(timestamp).toDate()); 85 | setRegistryHost(host); 86 | }}; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/builds/1/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DOCKER_TRIGGER_REPO_NAME 8 | csanchez/jenkins-swarm-slave 9 | 10 | 11 | DOCKER_TRIGGER_DOCKER_HUB_HOST 12 | registry.hub.example.com 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | csanchez/jenkins-swarm-slave 21 | {"push_data":{"pushed_at":1429028158,"images":["imagehash1","imagehash2","imagehash3"],"pusher":"csanchez"},"callback_url":"https://registry.hub.example.com/u/csanchez/jenkins-swarm-slave/hook/2g1gggacfij1c4a3jd00bf4fe3c3ec0da/","repository":{"status":"Active","description":"","is_trusted":true,"full_description":"# Jenkins swarm slave\n\n[`csanchez/jenkins-swarm-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-swarm-slave/)\n\nA [Jenkins swarm](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin) slave.\n\nFor a container with ssh enabled see\n[`csanchez/jenkins-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-slave/)\n\nFor a container with many build tools installed see\n[`maestrodev/build-agent`](https://registry.hub.docker.com/u/maestrodev/build-agent/)\n\n## Running\n\nTo run a Docker container passing [any parameters](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin#SwarmPlugin-AvailableOptions) to the slave\n\n docker run csanchez/jenkins-swarm-slave -master http://jenkins:8080 -username jenkins -password jenkins -executors 1\n\nLinking to the Jenkins master container there is no need to use `--master`\n\n docker run -d --name jenkins -p 8080:8080 csanchez/jenkins-swarm\n docker run -d --link jenkins:jenkins csanchez/jenkins-swarm-slave -username jenkins -password jenkins -executors 1\n\n\n# Building\n\n docker build -t csanchez/jenkins-swarm-slave .\n","repo_url":"https://registry.hub.example.com/u/csanchez/jenkins-swarm-slave/","owner":"csanchez","is_official":false,"is_private":false,"name":"jenkins-swarm-slave","namespace":"csanchez","star_count":2,"comment_count":0,"date_created":1410991410,"dockerfile":"FROM java:8u40-b22-jdk\n\nMAINTAINER Carlos Sanchez <carlos@apache.org>\n\nENV JENKINS_SWARM_VERSION 1.22\nENV HOME /home/jenkins-slave\n\nRUN useradd -c \"Jenkins Slave user\" -d $HOME -m jenkins-slave\nRUN curl --create-dirs -sSLo /usr/share/jenkins/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar http://maven.jenkins-ci.org/content/repositories/releases/org/jenkins-ci/plugins/swarm-client/$JENKINS_SWARM_VERSION/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar \\\n && chmod 755 /usr/share/jenkins\n\nCOPY jenkins-slave.sh /usr/local/bin/jenkins-slave.sh\n\nUSER jenkins-slave\n\nVOLUME /home/jenkins-slave\n\nENTRYPOINT [\"/usr/local/bin/jenkins-slave.sh\"]\n","repo_name":"csanchez/jenkins-swarm-slave"}} 22 | 1457967220599 23 | 24 | 25 | 26 | 27 | 28 | 1457947426000 29 | 1457967226370 30 | FAILURE 31 | 978 32 | US-ASCII 33 | false 34 | 35 | /var/jenkins_home/workspace/JenkinsSlaveTrigger 36 | 1.580.1 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/registry/notification/BackCompat102Test/jobs/JenkinsSlaveTrigger/builds/2/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DOCKER_TRIGGER_REPO_NAME 8 | csanchez/jenkins-swarm-slave 9 | 10 | 11 | DOCKER_TRIGGER_DOCKER_HUB_HOST 12 | registry.hub.example.com 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | csanchez/jenkins-swarm-slave 21 | {"push_data":{"pushed_at":1429028158,"images":["imagehash1","imagehash2","imagehash3"],"pusher":"csanchez"},"callback_url":"https://registry.hub.example.com/u/csanchez/jenkins-swarm-slave/hook/2g1gggacfij1c4a3jd00bf4fe3c3ec0da/","repository":{"status":"Active","description":"","is_trusted":true,"full_description":"# Jenkins swarm slave\n\n[`csanchez/jenkins-swarm-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-swarm-slave/)\n\nA [Jenkins swarm](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin) slave.\n\nFor a container with ssh enabled see\n[`csanchez/jenkins-slave`](https://registry.hub.docker.com/u/csanchez/jenkins-slave/)\n\nFor a container with many build tools installed see\n[`maestrodev/build-agent`](https://registry.hub.docker.com/u/maestrodev/build-agent/)\n\n## Running\n\nTo run a Docker container passing [any parameters](https://wiki.jenkins-ci.org/display/JENKINS/Swarm+Plugin#SwarmPlugin-AvailableOptions) to the slave\n\n docker run csanchez/jenkins-swarm-slave -master http://jenkins:8080 -username jenkins -password jenkins -executors 1\n\nLinking to the Jenkins master container there is no need to use `--master`\n\n docker run -d --name jenkins -p 8080:8080 csanchez/jenkins-swarm\n docker run -d --link jenkins:jenkins csanchez/jenkins-swarm-slave -username jenkins -password jenkins -executors 1\n\n\n# Building\n\n docker build -t csanchez/jenkins-swarm-slave .\n","repo_url":"https://registry.hub.example.com/u/csanchez/jenkins-swarm-slave/","owner":"csanchez","is_official":false,"is_private":false,"name":"jenkins-swarm-slave","namespace":"csanchez","star_count":2,"comment_count":0,"date_created":1410991410,"dockerfile":"FROM java:8u40-b22-jdk\n\nMAINTAINER Carlos Sanchez <carlos@apache.org>\n\nENV JENKINS_SWARM_VERSION 1.22\nENV HOME /home/jenkins-slave\n\nRUN useradd -c \"Jenkins Slave user\" -d $HOME -m jenkins-slave\nRUN curl --create-dirs -sSLo /usr/share/jenkins/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar http://maven.jenkins-ci.org/content/repositories/releases/org/jenkins-ci/plugins/swarm-client/$JENKINS_SWARM_VERSION/swarm-client-$JENKINS_SWARM_VERSION-jar-with-dependencies.jar \\\n && chmod 755 /usr/share/jenkins\n\nCOPY jenkins-slave.sh /usr/local/bin/jenkins-slave.sh\n\nUSER jenkins-slave\n\nVOLUME /home/jenkins-slave\n\nENTRYPOINT [\"/usr/local/bin/jenkins-slave.sh\"]\n","repo_name":"csanchez/jenkins-swarm-slave"}} 22 | 1457967297196 23 | 24 | 25 | 26 | 27 | 28 | 1457947506000 29 | 1457967306445 30 | SUCCESS 31 | 26 32 | US-ASCII 33 | false 34 | 35 | /var/jenkins_home/workspace/JenkinsSlaveTrigger 36 | 1.580.1 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/dockerregistry/DockerRegistryPushNotification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, HolidayCheck AG. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook.dockerregistry; 25 | 26 | import edu.umd.cs.findbugs.annotations.CheckForNull; 27 | import hudson.Util; 28 | import hudson.model.Cause; 29 | import hudson.model.ParameterValue; 30 | import hudson.model.StringParameterValue; 31 | import org.apache.commons.lang.StringUtils; 32 | import org.jenkinsci.plugins.registry.notification.webhook.PushNotification; 33 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookPayload; 34 | 35 | import java.util.HashSet; 36 | import java.util.Set; 37 | import java.util.logging.Logger; 38 | 39 | /** 40 | * Received notification from the Docker Registry web hook. 41 | * See Reference 42 | */ 43 | public class DockerRegistryPushNotification extends PushNotification { 44 | private static final long serialVersionUID = 207798312860576090L; 45 | private static final Logger logger = Logger.getLogger(DockerRegistryPushNotification.class.getName()); 46 | public static final String KEY_REPO_NAME = WebHookPayload.PREFIX + "REPO_NAME"; 47 | public static final String KEY_TAG = WebHookPayload.PREFIX + "TAG"; 48 | public static final String KEY_DOCKER_REGISTRY_HOST = WebHookPayload.PREFIX + "DOCKER_REGISTRY_HOST"; 49 | private String registryHost; 50 | private String tag; 51 | 52 | public DockerRegistryPushNotification(DockerRegistryWebHookPayload webHookPayload, String repoName) { 53 | super(webHookPayload); 54 | this.repoName = repoName; 55 | } 56 | 57 | @CheckForNull 58 | public String getTag() { 59 | return tag; 60 | } 61 | 62 | public void setTag(String tag) { 63 | this.tag = tag; 64 | } 65 | 66 | @CheckForNull 67 | public String getRegistryHost() { 68 | return registryHost; 69 | } 70 | 71 | public void setRegistryHost(String registryHost) { 72 | this.registryHost = registryHost; 73 | } 74 | 75 | @Override 76 | public Cause getCause() { 77 | return new DockerRegistryWebHookCause(this); 78 | } 79 | 80 | @Override 81 | public Set getRunParameters() { 82 | Set parameters = new HashSet(); 83 | parameters.add(new StringParameterValue(KEY_REPO_NAME, getRepoName())); 84 | String tag = getTag(); 85 | if (!StringUtils.isBlank(tag)) { 86 | parameters.add(new StringParameterValue(KEY_TAG, tag)); 87 | } 88 | String host = getRegistryHost(); 89 | if (!StringUtils.isBlank(host)) { 90 | parameters.add(new StringParameterValue(KEY_DOCKER_REGISTRY_HOST, host)); 91 | } 92 | return parameters; 93 | } 94 | 95 | @Override 96 | public String getCauseMessage() { 97 | return "Docker image " + getRepoName() + " has been rebuilt by DockerRegistry@" + getRegistryHost(); 98 | } 99 | 100 | public String sha() { 101 | return Util.getDigestOf("dockerRegistryNotification:" + repoName + Long.toBinaryString(getReceived())); 102 | } 103 | 104 | @Override 105 | public String getShortDescription() { 106 | return String.format("push of %s to DockerRegistry@%s", getRepoName(), getRegistryHost()); 107 | 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/token/ApiTokens/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 |
10 |
${%There are no access tokens yet.}
11 |
12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 25 | ${%Revoke} 26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/PushNotification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification.webhook; 25 | 26 | import edu.umd.cs.findbugs.annotations.CheckForNull; 27 | import hudson.Util; 28 | import hudson.model.Cause; 29 | import hudson.model.ParameterValue; 30 | import hudson.model.Run; 31 | 32 | import java.util.Collections; 33 | import java.util.Date; 34 | import java.util.Set; 35 | 36 | public abstract class PushNotification { 37 | 38 | private final WebHookPayload webHookPayload; 39 | 40 | protected String repoName; 41 | private Date pushedAt; 42 | 43 | CallbackHandler callbackHandler = new CallbackHandler() { 44 | @Override 45 | public void notify(PushNotification pushNotification, Run run) { 46 | } 47 | }; 48 | 49 | public PushNotification(WebHookPayload webHookPayload) { 50 | this.webHookPayload = webHookPayload; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | 58 | PushNotification that = (PushNotification) o; 59 | 60 | if (webHookPayload != null ? !webHookPayload.equals(that.webHookPayload) : that.webHookPayload != null) 61 | return false; 62 | if (repoName != null ? !repoName.equals(that.repoName) : that.repoName != null) return false; 63 | return !(pushedAt != null ? !pushedAt.equals(that.pushedAt) : that.pushedAt != null); 64 | 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | int result = webHookPayload != null ? webHookPayload.hashCode() : 0; 70 | result = 31 * result + (repoName != null ? repoName.hashCode() : 0); 71 | result = 31 * result + (pushedAt != null ? pushedAt.hashCode() : 0); 72 | return result; 73 | } 74 | 75 | abstract public Cause getCause(); 76 | 77 | /** 78 | * Provide parameters to be put into a build. 79 | * @return the parameters 80 | * @deprecated misspelled and wrong context naming. Use {@link #getRunParameters()} 81 | */ 82 | @Deprecated 83 | public Set getJobParamerers() { 84 | if (Util.isOverridden(PushNotification.class, getClass(), "getRunParameters")) { 85 | return getRunParameters(); 86 | } 87 | return Collections.emptySet(); 88 | } 89 | 90 | public Set getRunParameters() { 91 | if (Util.isOverridden(PushNotification.class, getClass(), "getJobParamerers")) { 92 | return getJobParamerers(); 93 | } 94 | return Collections.emptySet(); 95 | } 96 | 97 | abstract public String getCauseMessage(); 98 | 99 | /** 100 | * String like "username/reponame" 101 | * @return the name of the repo 102 | */ 103 | public String getRepoName() { 104 | return repoName; 105 | } 106 | 107 | abstract public String sha(); 108 | 109 | @CheckForNull 110 | public Date getPushedAt() { 111 | return new Date(this.pushedAt.getTime()); 112 | } 113 | 114 | public void setPushedAt(Date pushedAt) { 115 | this.pushedAt = new Date(pushedAt.getTime()); 116 | } 117 | 118 | public WebHookPayload getWebHookPayload() { 119 | return webHookPayload; 120 | } 121 | 122 | public long getReceived() { 123 | return webHookPayload.getReceived(); 124 | } 125 | 126 | abstract public String getShortDescription(); 127 | 128 | public CallbackHandler getCallbackHandler() { 129 | return callbackHandler; 130 | } 131 | 132 | public void setCallbackHandler(CallbackHandler callbackHandler) { 133 | this.callbackHandler = callbackHandler; 134 | } 135 | 136 | abstract public String getRegistryHost(); 137 | } 138 | -------------------------------------------------------------------------------- /src/test/resources/public-repository-payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "push_data": { 3 | "pushed_at": 1429028577, 4 | "images": [ 5 | "imagehash1", 6 | "imagehash2", 7 | "imagehash3" 8 | ], 9 | "pusher": "csanchez" 10 | }, 11 | "callback_url": "https://registry.hub.docker.com/_/busybox/hook/2fge0b054e32b45iidb3ac4e2caefabja/", 12 | "repository": { 13 | "status": "Active", 14 | "description": "Busybox base image.", 15 | "is_trusted": false, 16 | "full_description": "# Supported tags and respective `Dockerfile` links\n\n-\t[`buildroot-2013.08.1` (*Dockerfile*)](https://github.com/jpetazzo/docker-busybox/blob/220a689ce359914af3e08a698d1d74ec7aa0a444/Dockerfile)\n-\t[`buildroot-2014.02`, `latest` (*Dockerfile*)](https://github.com/jpetazzo/docker-busybox/blob/91641afe424df5e838bac254d43e09f051ab8c3e/Dockerfile)\n-\t[`ubuntu-12.04` (*Dockerfile*)](https://github.com/jpetazzo/docker-busybox/blob/4f6cb64c3b3255c58021dc75100da0088796a108/Dockerfile)\n-\t[`ubuntu-14.04` (*Dockerfile*)](https://github.com/jpetazzo/docker-busybox/blob/ca435164f45c40d761fad9ef9b5a76a6ba0d5f1a/Dockerfile)\n\nFor more information about this image and its history, please see the [relevant manifest file (`library/busybox`)](https://github.com/docker-library/official-images/blob/master/library/busybox) in the [`docker-library/official-images` GitHub repo](https://github.com/docker-library/official-images).\n\n# What is BusyBox? The Swiss Army Knife of Embedded Linux\n\nAt about 2.5 Mb in size, [BusyBox](http://www.busybox.net/) is a very good ingredient to craft space-efficient distributions.\n\nBusyBox combines tiny versions of many common UNIX utilities into a single small executable. It provides replacements for most of the utilities you usually find in GNU fileutils, shellutils, etc. The utilities in BusyBox generally have fewer options than their full-featured GNU cousins; however, the options that are included provide the expected functionality and behave very much like their GNU counterparts. BusyBox provides a fairly complete environment for any small or embedded system.\n\n> [wikipedia.org/wiki/BusyBox](https://en.wikipedia.org/wiki/BusyBox)\n\n![logo](https://raw.githubusercontent.com/docker-library/docs/master/busybox/logo.png)\n\n# How to use this image\n\n## Run BusyBox shell\n\n\tdocker run -it --rm busybox\n\nThis will drop you into an `sh` shell to allow you to do what you want inside a BusyBox system.\n\n## Create a `Dockerfile` for a binary\n\n\tFROM busybox\n\tCOPY ./my-static-binary /my-static-binary\n\tCMD [\"/my-static-binary\"]\n\nThis `Dockerfile` will allow you to create a minimal image for your statically compiled binary. You will have to compile the binary in some other place like another container.\n\n## More about this image\n\nThe tags of this image are built using two different methods. The `ubuntu` tags are using the `busybox-static` package from Ubuntu, adding a few support files so that it works in Docker. It's super fast to build (a minute or even less). The `buildroot` tags are going the long way: they use buildroot to craft a whole filesystem, with busybox but also all required libraries and other support files. It has a stronger guarantee of \"this will work\". It is also smaller because it's using uclibc, however it takes hours to build.\n\nHaving two totally different builders means that if one of the goes belly up, we can always fall-back on the other since this image is used in much of build testing of `docker` itself.\n\n# License\n\nView [license information](http://www.busybox.net/license.html) for the software contained in this image.\n\n# Supported Docker versions\n\nThis image is officially supported on Docker version 1.5.0.\n\nSupport for older versions (down to 1.0) is provided on a best-effort basis.\n\n# User Feedback\n\n## Documentation\n\nDocumentation for this image is stored in the [`busybox/` directory](https://github.com/docker-library/docs/tree/master/busybox) of the [`docker-library/docs` GitHub repo](https://github.com/docker-library/docs). Be sure to familiarize yourself with the [repository's `REAMDE.md` file](https://github.com/docker-library/docs/blob/master/README.md) before attempting a pull request.\n\n## Issues\n\nIf you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/jpetazzo/docker-busybox/issues).\n\nYou can also reach many of the official image maintainers via the `#docker-library` IRC channel on [Freenode](https://freenode.net).\n\n## Contributing\n\nYou are invited to contribute new features, fixes, or updates, large or small; we are always thrilled to receive pull requests, and do our best to process them as fast as we can.\n\nBefore you start to code, we recommend discussing your plans through a [GitHub issue](https://github.com/jpetazzo/docker-busybox/issues), especially for more ambitious contributions. This gives other contributors a chance to point you in the right direction, give you feedback on your design, and help you find out if someone else is working on the same thing.", 17 | "repo_url": "https://registry.hub.docker.com/_/busybox/", 18 | "owner": "library", 19 | "is_official": false, 20 | "is_private": false, 21 | "name": "busybox", 22 | "namespace": "library", 23 | "star_count": 161, 24 | "comment_count": 10, 25 | "date_created": 1367355282, 26 | "repo_name": "busybox" 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/Coordinator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification; 25 | 26 | import edu.umd.cs.findbugs.annotations.CheckForNull; 27 | import edu.umd.cs.findbugs.annotations.NonNull; 28 | import hudson.Extension; 29 | import hudson.ExtensionList; 30 | import hudson.model.Job; 31 | import hudson.model.Run; 32 | import hudson.model.TaskListener; 33 | import hudson.model.listeners.RunListener; 34 | import jenkins.model.Jenkins; 35 | import org.jenkinsci.plugins.registry.notification.webhook.PushNotification; 36 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookCause; 37 | import org.jenkinsci.plugins.registry.notification.webhook.dockerhub.DockerHubCallbackPayload; 38 | import org.jenkinsci.plugins.registry.notification.webhook.dockerhub.DockerHubWebHookCause; 39 | 40 | import java.io.IOException; 41 | import java.util.concurrent.ExecutionException; 42 | import java.util.logging.Level; 43 | import java.util.logging.Logger; 44 | 45 | /** 46 | * Coordinates and sends back a {@link DockerHubCallbackPayload} 47 | * to Docker Hub when all builds are finalized. 48 | */ 49 | @Extension 50 | public class Coordinator extends RunListener> { 51 | 52 | public void onTriggered(@NonNull Job job, @NonNull PushNotification pushNotification) { 53 | logger.log(Level.FINER, "Job {0} triggered for payload: {1}", new Object[]{job.getFullDisplayName(), pushNotification}); 54 | TriggerStore.getInstance().triggered(pushNotification, job); 55 | } 56 | 57 | @Override 58 | public void onStarted(@NonNull Run run, @NonNull TaskListener listener) { 59 | DockerHubWebHookCause cause = run.getCause(DockerHubWebHookCause.class); 60 | if (cause != null) { 61 | logger.log(Level.FINER, "Build {0} started for cause: {1}", new Object[]{run.getFullDisplayName(), cause}); 62 | TriggerStore.getInstance().started(cause.getPushNotification(), run); 63 | } 64 | } 65 | 66 | @Override 67 | public void onFinalized(@NonNull Run run) { 68 | WebHookCause cause = run.getCause(WebHookCause.class); 69 | if (cause != null) { 70 | logger.log(Level.FINER, "Build {0} done for cause: [{1}]", new Object[]{run.getFullDisplayName(), cause}); 71 | TriggerStore.TriggerEntry entry = TriggerStore.getInstance().finalized(cause.getPushNotification(), run); 72 | if (entry != null) { 73 | if(entry.areAllDone()) { 74 | logger.log(Level.FINE, "All builds for [{0}] are done, preparing callback to Docker Hub", cause); 75 | try { 76 | sendResponse(cause.getPushNotification(), run); 77 | } catch (Exception e) { 78 | logger.log(Level.SEVERE, "Failed to update Docker Hub!", e); 79 | } 80 | } 81 | } else { 82 | logger.log(Level.INFO, "Failed to do final evaluation of builds for cause [{0}]", cause); 83 | } 84 | } 85 | } 86 | 87 | private void sendResponse(@NonNull final PushNotification pushNotification, Run run) throws IOException, ExecutionException, InterruptedException { 88 | pushNotification.getCallbackHandler().notify(pushNotification, run); 89 | } 90 | 91 | @Override 92 | public void onDeleted(@NonNull Run run) { 93 | DockerHubWebHookCause cause = run.getCause(DockerHubWebHookCause.class); 94 | if (cause != null) { 95 | TriggerStore.getInstance().removed(cause.getPushNotification(), run); 96 | } 97 | } 98 | 99 | @CheckForNull 100 | public static Coordinator getInstance() { 101 | Jenkins j = Jenkins.getInstance(); 102 | if (j != null) { 103 | ExtensionList list = j.getExtensionList(Coordinator.class); 104 | if (list != null && !list.isEmpty()) { 105 | return list.get(0); 106 | } 107 | } 108 | return null; 109 | } 110 | 111 | private static final Logger logger = Logger.getLogger(Coordinator.class.getName()); 112 | } 113 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | 4.0.0 29 | 30 | org.jenkins-ci.plugins 31 | plugin 32 | 5.7 33 | 34 | 35 | 36 | org.jenkins-ci.plugins 37 | dockerhub-notification 38 | ${changelist} 39 | hpi 40 | 41 | CloudBees Docker Hub/Registry Notification 42 | https://github.com/jenkinsci/${project.artifactId}-plugin 43 | 44 | 45 | 999999-SNAPSHOT 46 | 47 | 2.479 48 | ${jenkins.baseline}.1 49 | jenkinsci/${project.artifactId}-plugin 50 | 51 | 52 | 53 | scm:git:https://github.com/${gitHubRepo}.git 54 | scm:git:git@github.com:${gitHubRepo}.git 55 | https://github.com/${gitHubRepo} 56 | ${scmTag} 57 | 58 | 59 | 60 | 61 | MIT 62 | https://opensource.org/licenses/MIT 63 | repo 64 | 65 | 66 | 67 | 68 | 69 | repo.jenkins-ci.org 70 | https://repo.jenkins-ci.org/public/ 71 | 72 | 73 | 74 | 75 | repo.jenkins-ci.org 76 | https://repo.jenkins-ci.org/public/ 77 | 78 | 79 | 80 | 81 | 82 | org.jenkins-ci.plugins 83 | docker-commons 84 | 85 | 86 | io.jenkins.plugins 87 | okhttp-api 88 | 89 | 90 | io.jenkins.plugins 91 | joda-time-api 92 | 93 | 94 | org.jenkins-ci.plugins 95 | structs 96 | 97 | 98 | io.jenkins 99 | configuration-as-code 100 | test 101 | 102 | 103 | io.jenkins.configuration-as-code 104 | test-harness 105 | test 106 | 107 | 108 | 109 | org.jenkins-ci.plugins 110 | docker-workflow 111 | test 112 | 113 | 114 | 115 | 116 | 117 | 118 | io.jenkins.tools.bom 119 | bom-${jenkins.baseline}.x 120 | 3893.v213a_42768d35 121 | import 122 | pom 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/DockerPullImageBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015, CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.jenkinsci.plugins.registry.notification; 25 | 26 | import edu.umd.cs.findbugs.annotations.NonNull; 27 | import hudson.Extension; 28 | import hudson.Launcher; 29 | import hudson.EnvVars; 30 | import hudson.model.Build; 31 | import hudson.model.BuildListener; 32 | import hudson.model.AbstractBuild; 33 | import hudson.model.AbstractProject; 34 | import hudson.model.Job; 35 | import hudson.model.Project; 36 | import hudson.tasks.BuildStepDescriptor; 37 | import hudson.tasks.Builder; 38 | 39 | import java.io.IOException; 40 | import java.util.Collection; 41 | import java.util.Collections; 42 | import java.util.HashSet; 43 | import java.util.Set; 44 | 45 | import org.jenkinsci.plugins.docker.commons.DockerImageExtractor; 46 | import org.jenkinsci.plugins.docker.commons.credentials.KeyMaterial; 47 | import org.jenkinsci.plugins.docker.commons.credentials.DockerRegistryEndpoint; 48 | import org.kohsuke.stapler.DataBoundConstructor; 49 | 50 | 51 | /** 52 | * Pull and Run specified Docker image 53 | */ 54 | public class DockerPullImageBuilder extends Builder { 55 | 56 | private final String image; 57 | private final DockerRegistryEndpoint registry; 58 | 59 | @DataBoundConstructor 60 | public DockerPullImageBuilder(DockerRegistryEndpoint registry, String image) { 61 | this.image = image; 62 | this.registry = registry; 63 | } 64 | 65 | public String getImage() { 66 | return image; 67 | } 68 | 69 | public DockerRegistryEndpoint getRegistry() { 70 | return registry; 71 | } 72 | 73 | @Override 74 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { 75 | 76 | // TODO could maybe use Docker REST API, need first to check Java can talk with linux sockets 77 | // TODO maybe use DockerHost API 78 | int status = 0; 79 | KeyMaterial key = null; 80 | final EnvVars env = build.getEnvironment(listener); 81 | String expandedImage = env.expand(image); 82 | try { 83 | // get Docker registry credentials 84 | key = registry.newKeyMaterialFactory(build).materialize(); 85 | 86 | status = launcher.launch() 87 | .cmds("docker", "pull", registry.imageName(expandedImage)).envs(key.env()) 88 | .writeStdin().stdout(listener.getLogger()).stderr(listener.getLogger()).join(); 89 | if (status != 0) { 90 | throw new RuntimeException("Failed to pull docker image"); 91 | } 92 | } catch (IOException e) { 93 | throw new RuntimeException("Failed to pull docker image", e); 94 | } catch (InterruptedException e) { 95 | throw new RuntimeException("Failed to pull docker image", e); 96 | } finally { 97 | if (key != null) { 98 | key.close(); 99 | } 100 | } 101 | 102 | listener.getLogger().println("docker pull " + image); 103 | return true; 104 | } 105 | 106 | @Extension 107 | public static final class DescriptorImpl extends BuildStepDescriptor { 108 | 109 | public boolean isApplicable(Class aClass) { 110 | return true; 111 | } 112 | 113 | /** 114 | * This human readable image is used in the configuration screen. 115 | */ 116 | public String getDisplayName() { 117 | return Messages.DockerPullImageBuilder_DisplayName(); 118 | } 119 | } 120 | 121 | @Extension 122 | public static final class ImageExtractor extends DockerImageExtractor { 123 | @NonNull 124 | @Override 125 | public Collection getDockerImagesUsedByJob(@NonNull Job job) { 126 | if (job instanceof Project) { 127 | Project project = (Project)job; 128 | Set images = new HashSet(); 129 | // check DockerHub build step for matching image ID 130 | for (Builder b : project.getBuilders()) { 131 | if (b instanceof DockerPullImageBuilder) { 132 | images.add(((DockerPullImageBuilder)b).getImage()); 133 | } 134 | } 135 | return images; 136 | } else { 137 | return Collections.emptySet(); 138 | } 139 | } 140 | } 141 | } 142 | 143 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/registry/notification/token/ApiTokens/resources.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Registering the onclick handler on all the "Revoke" buttons for API Token. 3 | */ 4 | Behaviour.specify(".dockerhub-api-token-revoke-button", 'ApiTokens', 0, function(button) { 5 | // DEV MEMO: 6 | // While un-inlining the onclick handler, we are trying to avoid modifying the existing source code and functions. 7 | // In order to keep consistency with the existing code, we share the dockerhub-api-token-revoke-button with the revokeDockerHubApiToken method 8 | // which is then navigating the DOM in order to retrieve a the token to revoke. 9 | // While this could be done setting additional data on the button itself and retrieving it without DOM navigation, 10 | // this would need to be done in another contribution. 11 | button.onclick = (_) => revokeDockerHubApiToken(button); 12 | }) 13 | 14 | function revokeDockerHubApiToken(anchorRevoke) { 15 | const repeatedChunk = anchorRevoke.closest('.repeated-chunk'); 16 | const apiTokenList = repeatedChunk.closest('.dockerhub-api-token-list'); 17 | const confirmMessage = anchorRevoke.getAttribute('data-confirm'); 18 | const targetUrl = anchorRevoke.getAttribute('data-target-url'); 19 | const inputUuid = repeatedChunk.querySelector('.dockerhub-api-token-uuid-input'); 20 | const apiTokenUuid = inputUuid.value; 21 | 22 | if (confirm(confirmMessage)) { 23 | fetch(targetUrl, { 24 | headers: crumb.wrap({ 25 | "Content-Type": "application/x-www-form-urlencoded", 26 | }), 27 | method: "post", 28 | body: new URLSearchParams({apiTokenUuid: apiTokenUuid}), 29 | }).then((rsp) => { 30 | if (rsp.ok) { 31 | repeatedChunk.remove(); 32 | adjustEmptyListMessage(apiTokenList); 33 | } 34 | }); 35 | } 36 | 37 | return false; 38 | } 39 | 40 | /** 41 | * Registering the onclick handler on all the "Generate" buttons for API Token. 42 | */ 43 | Behaviour.specify(".dockerhub-api-token-save-button", 'ApiTokens', 0, function(buttonContainer) { 44 | // DEV MEMO: 45 | // While un-inlining the onclick handler, we are trying to avoid modifying the existing source code and functions. 46 | // In order to keep consistency with the existing code, we add our onclick handler on the button element which is contained in the 47 | // dockerhub-api-token-save-button that we identify. While this could be refactored to directly identify the button, this would need to be done in an other 48 | // contribution. 49 | const button = buttonContainer.getElementsByTagName('button')[0]; 50 | button.onclick = (_) => saveDockerHubApiToken(button); 51 | }) 52 | 53 | function saveDockerHubApiToken(button){ 54 | if (button.classList.contains('request-pending')) { 55 | // avoid multiple requests to be sent if user is clicking multiple times 56 | return; 57 | } 58 | button.classList.add('request-pending'); 59 | const targetUrl = button.getAttribute('data-target-url'); 60 | const repeatedChunk = button.closest('.repeated-chunk'); 61 | const apiTokenList = repeatedChunk.closest('.dockerhub-api-token-list'); 62 | const nameInput = repeatedChunk.querySelector('.dockerhub-api-token-name-input'); 63 | const apiTokenName = nameInput.value; 64 | 65 | fetch(targetUrl, { 66 | headers: crumb.wrap({ 67 | "Content-Type": "application/x-www-form-urlencoded", 68 | }), 69 | method: "post", 70 | body: new URLSearchParams({apiTokenName: apiTokenName}), 71 | }).then((rsp) => { 72 | if (rsp.ok) { 73 | rsp.json().then((json) => { 74 | const { name, value, uuid } = json.data; 75 | nameInput.value = name; 76 | 77 | const apiTokenValueSpan = repeatedChunk.querySelector('.dockerhub-new-api-token-value'); 78 | apiTokenValueSpan.innerText = value; 79 | apiTokenValueSpan.classList.remove('hidden'); 80 | 81 | const apiTokenCopyButton = repeatedChunk.querySelector('.copy-button'); 82 | apiTokenCopyButton.setAttribute('text', value); 83 | apiTokenCopyButton.classList.remove('hidden'); 84 | 85 | const uuidInput = repeatedChunk.querySelector('.dockerhub-api-token-uuid-input'); 86 | uuidInput.value = uuid; 87 | 88 | const warningMessage = repeatedChunk.querySelector('.dockerhub-api-token-warning-message'); 89 | warningMessage.classList.remove('hidden'); 90 | 91 | // we do not want to allow user to create twice api token using same name by mistake 92 | button.remove(); 93 | 94 | const revokeButton = repeatedChunk.querySelector('.dockerhub-api-token-revoke-button'); 95 | revokeButton.classList.remove('hidden'); 96 | 97 | const cancelButton = repeatedChunk.querySelector('.dockerhub-api-token-cancel-button'); 98 | cancelButton.classList.add('hidden'); 99 | 100 | repeatedChunk.classList.add('dockerhub-api-token-list-fresh-item'); 101 | 102 | adjustEmptyListMessage(apiTokenList); 103 | }); 104 | } 105 | }); 106 | } 107 | 108 | function adjustEmptyListMessage(apiTokenList) { 109 | const emptyListMessageClassList = apiTokenList.querySelector('.dockerhub-api-token-list-empty-item').classList; 110 | 111 | const apiTokenListLength = apiTokenList.querySelectorAll('.dockerhub-api-token-list-existing-item, .dockerhub-api-token-list-fresh-item').length; 112 | if (apiTokenListLength >= 1) { 113 | emptyListMessageClassList.add("hidden"); 114 | } else { 115 | emptyListMessageClassList.remove("hidden"); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/registry/notification/webhook/dockerhub/DockerHubWebHookPayload.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.registry.notification.webhook.dockerhub; 2 | 3 | import edu.umd.cs.findbugs.annotations.CheckForNull; 4 | import edu.umd.cs.findbugs.annotations.NonNull; 5 | import hudson.model.Run; 6 | import net.sf.json.JSONObject; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.jenkinsci.plugins.registry.notification.TriggerStore; 9 | import org.jenkinsci.plugins.registry.notification.webhook.CallbackHandler; 10 | import org.jenkinsci.plugins.registry.notification.webhook.Http; 11 | import org.jenkinsci.plugins.registry.notification.webhook.PushNotification; 12 | import org.jenkinsci.plugins.registry.notification.webhook.WebHookPayload; 13 | 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.Date; 17 | import java.util.concurrent.ExecutionException; 18 | import java.util.logging.Level; 19 | import java.util.logging.Logger; 20 | 21 | public class DockerHubWebHookPayload extends WebHookPayload { 22 | private static final Logger logger = Logger.getLogger(DockerHubWebHookPayload.class.getName()); 23 | 24 | private Date pushedAt = null; 25 | 26 | public DockerHubWebHookPayload(@NonNull String repoName, final @CheckForNull JSONObject data) { 27 | super(); 28 | setData(data); 29 | if (data != null) { 30 | setJson(data.toString()); 31 | } 32 | this.pushNotifications.add(createPushNotification(repoName, data)); 33 | } 34 | 35 | @Override 36 | protected Object readResolve() { 37 | super.readResolve(); 38 | if (this.pushNotifications == null || this.pushNotifications.isEmpty()) { 39 | this.pushNotifications = new ArrayList(); 40 | String repoName = ""; 41 | JSONObject data = this.getData(); 42 | if (data != null) { 43 | JSONObject repository = data.optJSONObject("repository"); 44 | if (repository != null) { 45 | if (repository.has("repo_name")) { 46 | repoName = repository.getString("repo_name"); 47 | } 48 | } 49 | this.pushNotifications.add(createPushNotification(repoName, data)); 50 | } 51 | } 52 | return this; 53 | } 54 | 55 | /** 56 | * Creates the object from the json payload 57 | * 58 | * @param data the json payload 59 | * @throws net.sf.json.JSONException if the key {@code repository.repo_name} doesn't exist. 60 | */ 61 | public DockerHubWebHookPayload(@NonNull JSONObject data) { 62 | super(); 63 | setData(data); 64 | if (data != null) { 65 | setJson(data.toString()); 66 | } 67 | JSONObject repository = data.getJSONObject("repository"); 68 | this.pushNotifications.add(createPushNotification(repository.getString("repo_name"), data)); 69 | } 70 | 71 | private DockerHubPushNotification createPushNotification(@NonNull final String repoName, @CheckForNull final JSONObject data) { 72 | final DockerHubPushNotification dockerHubPushNotification = new DockerHubPushNotification(this, repoName); 73 | if(data != null) { 74 | dockerHubPushNotification.setCallbackUrl(data.optString("callback_url")); 75 | JSONObject push_data = data.optJSONObject("push_data"); 76 | if (push_data != null) { 77 | try { 78 | long pushed_at = push_data.optLong("pushed_at"); 79 | if (pushed_at > 0) { 80 | dockerHubPushNotification.setPushedAt(new Date(pushed_at * 1000)); 81 | } 82 | } catch (Exception e) { 83 | // ignore 84 | } 85 | } 86 | dockerHubPushNotification.setCallbackHandler(new WebHookPayloadCallback(dockerHubPushNotification)); 87 | } 88 | return dockerHubPushNotification; 89 | } 90 | 91 | public DockerHubWebHookPayload(@NonNull String repoName) { 92 | this(repoName, null); 93 | } 94 | 95 | public static final class WebHookPayloadCallback implements CallbackHandler { 96 | final DockerHubPushNotification dockerHubPushNotification; 97 | 98 | public WebHookPayloadCallback(DockerHubPushNotification dockerHubPushNotification) { 99 | this.dockerHubPushNotification = dockerHubPushNotification; 100 | } 101 | 102 | @Override 103 | public void notify(PushNotification pushNotification, Run run) throws InterruptedException, ExecutionException, IOException { 104 | final String callbackUrl = dockerHubPushNotification.getCallbackUrl(); 105 | TriggerStore.TriggerEntry entry = TriggerStore.getInstance().finalized(dockerHubPushNotification, run); 106 | if(entry != null) { 107 | DockerHubCallbackPayload callback = DockerHubCallbackPayload.from(entry); 108 | if (callback != null) { 109 | if (!StringUtils.isBlank(callbackUrl)) { 110 | logger.log(Level.FINE, "Sending callback to Docker Hub"); 111 | logger.log(Level.FINER, "Callback: {0}", callback); 112 | int response = Http.post(callbackUrl, callback.toJSON()); 113 | logger.log(Level.FINE, "Docker Hub returned {0}", response); 114 | return; 115 | } 116 | logger.log(Level.WARNING, "No callback URL specified in {0}", pushNotification); 117 | } 118 | } 119 | logger.log(Level.WARNING, "Failed to prepare Docker Hub callback payload for {0}", pushNotification); 120 | } 121 | } 122 | 123 | } 124 | --------------------------------------------------------------------------------