clazz) throws IOException {
63 | ObjectMapper mapper = new ObjectMapper();
64 | return mapper.readValue(response, clazz);
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestSource.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 com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest;
25 |
26 | import org.codehaus.jackson.annotate.JsonIgnoreProperties;
27 | import org.codehaus.jackson.annotate.JsonProperty;
28 |
29 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBranch;
30 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit;
31 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource;
32 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
33 | import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranch;
34 | import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerCommit;
35 | import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository;
36 |
37 | @JsonIgnoreProperties(ignoreUnknown = true)
38 | public class BitbucketServerPullRequestSource implements BitbucketPullRequestSource {
39 |
40 | @JsonProperty("latestCommit")
41 | private String commitHash;
42 |
43 | @JsonProperty("displayId")
44 | private String branchName;
45 |
46 | private BitbucketServerRepository repository;
47 |
48 | @Override
49 | public BitbucketRepository getRepository() {
50 | return repository;
51 | }
52 |
53 | @Override
54 | public BitbucketBranch getBranch() {
55 | return new BitbucketServerBranch(branchName, commitHash);
56 | }
57 |
58 | @Override
59 | public BitbucketCommit getCommit() {
60 | return new BitbucketServerCommit(commitHash);
61 | }
62 |
63 | public void setCommitHash(String commitHash) {
64 | this.commitHash = commitHash;
65 | }
66 |
67 | public void setBranchName(String branchName) {
68 | this.branchName = branchName;
69 | }
70 |
71 | public void setRepository(BitbucketServerRepository repository) {
72 | this.repository = repository;
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValueRepository.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 com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest;
25 |
26 | import org.codehaus.jackson.annotate.JsonIgnoreProperties;
27 | import org.codehaus.jackson.annotate.JsonProperty;
28 |
29 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBranch;
30 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit;
31 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource;
32 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
33 | import com.cloudbees.jenkins.plugins.bitbucket.client.branch.BitbucketCloudBranch;
34 | import com.cloudbees.jenkins.plugins.bitbucket.client.branch.BitbucketCloudCommit;
35 | import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketCloudRepository;
36 |
37 | @JsonIgnoreProperties(ignoreUnknown = true)
38 | public class BitbucketPullRequestValueRepository implements BitbucketPullRequestSource {
39 | private BitbucketCloudRepository repository;
40 | private BitbucketCloudBranch branch;
41 | private BitbucketCloudCommit commit;
42 |
43 | @Override
44 | @JsonProperty("repository")
45 | public BitbucketRepository getRepository() {
46 | return repository;
47 | }
48 |
49 | @JsonProperty("repository")
50 | public void setRepository(BitbucketCloudRepository repository) {
51 | this.repository = repository;
52 | }
53 |
54 | @Override
55 | @JsonProperty("branch")
56 | public BitbucketBranch getBranch() {
57 | return branch;
58 | }
59 |
60 | @JsonProperty("branch")
61 | public void setBranch(BitbucketCloudBranch branch) {
62 | this.branch = branch;
63 | }
64 |
65 | @Override
66 | @JsonProperty("commit")
67 | public BitbucketCommit getCommit() {
68 | return commit;
69 | }
70 |
71 | @JsonProperty("commit")
72 | public void setCommit(BitbucketCloudCommit commit) {
73 | this.commit = commit;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookEventType.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 com.cloudbees.jenkins.plugins.bitbucket.hooks;
25 |
26 | import edu.umd.cs.findbugs.annotations.CheckForNull;
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 |
29 | /**
30 | * Bitbucket hooks types managed by this plugin.
31 | */
32 | public enum HookEventType {
33 |
34 | /**
35 | * See EventPayloads-Push
36 | */
37 | PUSH("repo:push", PushHookProcessor.class),
38 |
39 | /**
40 | * See EventPayloads-Created
41 | */
42 | PULL_REQUEST_CREATED("pullrequest:created", PullRequestHookProcessor.class),
43 |
44 | /**
45 | * See EventPayloads-Updated
46 | */
47 | PULL_REQUEST_UPDATED("pullrequest:updated", PullRequestHookProcessor.class);
48 |
49 | private String key;
50 | private Class> clazz;
51 |
52 | HookEventType(@NonNull String key, Class
clazz) {
53 | this.key = key;
54 | this.clazz = clazz;
55 | }
56 |
57 | @CheckForNull
58 | public static HookEventType fromString(String key) {
59 | for (HookEventType value : HookEventType.values()) {
60 | if (value.getKey().equals(key)) {
61 | return value;
62 | }
63 | }
64 | return null;
65 | }
66 |
67 | public HookProcessor getProcessor() {
68 | try {
69 | return (HookProcessor) clazz.newInstance();
70 | } catch (InstantiationException e) {
71 | throw new AssertionError("Can not instantiate hook payload processor: " + e.getMessage());
72 | } catch (IllegalAccessException e) {
73 | throw new AssertionError("Can not instantiate hook payload processor: " + e.getMessage());
74 | }
75 | }
76 |
77 | public String getKey() {
78 | return key;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/repository/BitbucketCloudRepository.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 com.cloudbees.jenkins.plugins.bitbucket.client.repository;
25 |
26 | import org.codehaus.jackson.annotate.JsonIgnoreProperties;
27 | import org.codehaus.jackson.annotate.JsonProperty;
28 |
29 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
30 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepositoryOwner;
31 |
32 | @JsonIgnoreProperties(ignoreUnknown = true)
33 | public class BitbucketCloudRepository implements BitbucketRepository {
34 |
35 | private String scm;
36 |
37 | @JsonProperty("full_name")
38 | private String fullName;
39 |
40 | private BitbucketCloudRepositoryOwner owner;
41 |
42 | @JsonProperty("updated_on")
43 | private String updatedOn;
44 |
45 | // JSON mapping added in setter because the field can not be called "private"
46 | private Boolean priv;
47 |
48 | @Override
49 | public String getScm() {
50 | return scm;
51 | }
52 |
53 | @Override
54 | public String getFullName() {
55 | return fullName;
56 | }
57 |
58 | @Override
59 | public BitbucketRepositoryOwner getOwner() {
60 | return owner;
61 | }
62 |
63 | public void setScm(String scm) {
64 | this.scm = scm;
65 | }
66 |
67 | public void setFullName(String fullName) {
68 | this.fullName = fullName;
69 | }
70 |
71 | public void setOwner(BitbucketCloudRepositoryOwner owner) {
72 | this.owner = owner;
73 | }
74 |
75 | public void setUpdatedOn(String updatedOn) {
76 | this.updatedOn = updatedOn;
77 | }
78 |
79 | @Override
80 | public String getOwnerName() {
81 | if (this.fullName != null) {
82 | return this.fullName.split("/")[0];
83 | }
84 | return null;
85 | }
86 |
87 | @Override
88 | public String getRepositoryName() {
89 | if (this.fullName != null) {
90 | return this.fullName.split("/")[1];
91 | }
92 | return null;
93 | }
94 |
95 | @Override
96 | public boolean isPrivate() {
97 | return priv;
98 | }
99 |
100 | @JsonProperty("is_private")
101 | public void setPrivate(Boolean priv) {
102 | this.priv = priv;
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/repository/BitbucketServerRepository.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 com.cloudbees.jenkins.plugins.bitbucket.server.client.repository;
25 |
26 | import org.codehaus.jackson.annotate.JsonIgnoreProperties;
27 | import org.codehaus.jackson.annotate.JsonProperty;
28 |
29 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
30 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepositoryOwner;
31 |
32 | @JsonIgnoreProperties(ignoreUnknown = true)
33 | public class BitbucketServerRepository implements BitbucketRepository {
34 |
35 | @JsonProperty("scmId")
36 | private String scm;
37 |
38 | private Project project;
39 |
40 | @JsonProperty("slug")
41 | private String repositoryName;
42 |
43 | // JSON mapping added in setter because the field can not be called "public"
44 | private Boolean publc;
45 |
46 | @Override
47 | public String getScm() {
48 | return scm;
49 | }
50 |
51 | @Override
52 | public String getFullName() {
53 | return project.getKey() + "/" + repositoryName;
54 | }
55 |
56 | @Override
57 | public BitbucketRepositoryOwner getOwner() {
58 | return new BitbucketServerRepositoryOwner(project.getKey(), project.getName());
59 | }
60 |
61 | @Override
62 | public String getOwnerName() {
63 | return project.getKey();
64 | }
65 |
66 | @Override
67 | public String getRepositoryName() {
68 | return repositoryName;
69 | }
70 |
71 | public void setProject(Project p) {
72 | this.project = p;
73 | }
74 |
75 | @Override
76 | public boolean isPrivate() {
77 | return !publc;
78 | }
79 |
80 | @JsonProperty("public")
81 | public void setPublic(Boolean publc) {
82 | this.publc = publc;
83 | }
84 |
85 | @JsonIgnoreProperties(ignoreUnknown = true)
86 | public static class Project {
87 |
88 | @JsonProperty
89 | private String key;
90 |
91 | @JsonProperty
92 | private String name;
93 |
94 | public String getKey() {
95 | return key;
96 | }
97 |
98 | public void setKey(String key) {
99 | this.key = key;
100 | }
101 |
102 | public String getName() {
103 | return name;
104 | }
105 |
106 | public void setName(String name) {
107 | this.name = name;
108 | }
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValue.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 com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest;
25 |
26 | import org.codehaus.jackson.annotate.JsonIgnoreProperties;
27 |
28 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest;
29 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource;
30 |
31 | @JsonIgnoreProperties(ignoreUnknown = true)
32 | public class BitbucketPullRequestValue implements BitbucketPullRequest {
33 | private BitbucketPullRequestValueRepository source;
34 | private String id;
35 | private String title;
36 |
37 | private Links links;
38 |
39 | private Author author;
40 |
41 | public BitbucketPullRequestSource getSource() {
42 | return source;
43 | }
44 |
45 | public void setSource(BitbucketPullRequestValueRepository source) {
46 | this.source = source;
47 | }
48 |
49 | public String getId() {
50 | return id;
51 | }
52 |
53 | public void setId(String id) {
54 | this.id = id;
55 | }
56 |
57 | @Override
58 | public String getTitle() {
59 | return title;
60 | }
61 |
62 | @Override
63 | public String getLink() {
64 | return links.html.href;
65 | }
66 |
67 | @Override
68 | public String getAuthorLogin() {
69 | return author.username;
70 | }
71 |
72 | public void setTitle(String title) {
73 | this.title = title;
74 | }
75 |
76 | public void setLinks(Links link) {
77 | this.links = link;
78 | }
79 |
80 | public void setAuthor(Author author) {
81 | this.author = author;
82 | }
83 |
84 | @JsonIgnoreProperties(ignoreUnknown = true)
85 | public static class Links {
86 | private Html html;
87 | public Links() {}
88 | // for tests
89 | public Links(String link) {
90 | html = new Html();
91 | html.href = link;
92 | }
93 | public void setHtml(Html html) {
94 | this.html = html;
95 | }
96 |
97 | @JsonIgnoreProperties(ignoreUnknown = true)
98 | private static class Html {
99 | public String href;
100 | public Html() {}
101 | }
102 | }
103 |
104 | @JsonIgnoreProperties(ignoreUnknown = true)
105 | public static class Author {
106 | private String username;
107 | public Author() {}
108 | public Author(String username) {
109 | this.username = username;
110 | }
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/UriResolverTest.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 com.cloudbees.jenkins.plugins.bitbucket;
25 |
26 | import org.junit.Test;
27 | import static org.junit.Assert.*;
28 |
29 | import java.net.MalformedURLException;
30 |
31 | public class UriResolverTest {
32 |
33 | @Test
34 | public void httpUriResolver() throws MalformedURLException {
35 | HttpsRepositoryUriResolver r = new HttpsRepositoryUriResolver(null);
36 | assertEquals("https://bitbucket.org/user1/repo1.git", r.getRepositoryUri("user1", "repo1", RepositoryType.GIT));
37 | assertEquals("https://bitbucket.org/user1/repo1", r.getRepositoryUri("user1", "repo1", RepositoryType.MERCURIAL));
38 | r = new HttpsRepositoryUriResolver("http://localhost:1234");
39 | assertEquals("http://localhost:1234/scm/user2/repo2.git", r.getRepositoryUri("user2", "repo2", RepositoryType.GIT));
40 | r = new HttpsRepositoryUriResolver("http://192.168.1.100:1234");
41 | assertEquals("http://192.168.1.100:1234/scm/user2/repo2.git", r.getRepositoryUri("user2", "repo2", RepositoryType.GIT));
42 | }
43 |
44 | @Test
45 | public void sshUriResolver() throws MalformedURLException {
46 | SshRepositoryUriResolver r = new SshRepositoryUriResolver(null, -1);
47 | assertEquals("git@bitbucket.org:user1/repo1.git", r.getRepositoryUri("user1", "repo1", RepositoryType.GIT));
48 | assertEquals("ssh://hg@bitbucket.org/user1/repo1", r.getRepositoryUri("user1", "repo1", RepositoryType.MERCURIAL));
49 | r = new SshRepositoryUriResolver("http://localhost:1234", 7999);
50 | assertEquals("ssh://git@localhost:7999/user2/repo2.git", r.getRepositoryUri("user2", "repo2", RepositoryType.GIT));
51 | r = new SshRepositoryUriResolver("http://myserver", 7999);
52 | assertEquals("ssh://git@myserver:7999/user2/repo2.git", r.getRepositoryUri("user2", "repo2", RepositoryType.GIT));
53 | }
54 |
55 | @Test(expected = IllegalStateException.class)
56 | public void httpUriResolverIllegalStates() throws MalformedURLException {
57 | HttpsRepositoryUriResolver r = new HttpsRepositoryUriResolver("http://localhost:1234");
58 | // Mercurial is not supported by Bitbucket Server
59 | r.getRepositoryUri("user1", "repo1", RepositoryType.MERCURIAL);
60 | }
61 |
62 | @Test(expected = IllegalStateException.class)
63 | public void sshUriResolverIllegalStates() throws MalformedURLException {
64 | SshRepositoryUriResolver r = new SshRepositoryUriResolver("http://localhost:1234", 7999);
65 | // Mercurial is not supported by Bitbucket Server
66 | r.getRepositoryUri("user1", "repo1", RepositoryType.MERCURIAL);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.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 com.cloudbees.jenkins.plugins.bitbucket.api;
25 |
26 | import org.codehaus.jackson.annotate.JsonIgnore;
27 | import org.codehaus.jackson.annotate.JsonIgnoreProperties;
28 | import org.kohsuke.accmod.Restricted;
29 | import org.kohsuke.accmod.restrictions.DoNotUse;
30 |
31 | @JsonIgnoreProperties(ignoreUnknown = true)
32 | public class BitbucketBuildStatus {
33 |
34 | /**
35 | * The commit hash to set the status on
36 | */
37 | @JsonIgnore
38 | private String hash;
39 |
40 | /**
41 | * Text shown in the UI
42 | */
43 | private String description;
44 |
45 | /**
46 | * One of: INPROGRESS|SUCCESSFUL|FAILED
47 | */
48 | private String state;
49 |
50 | /**
51 | * The URL to liunk from the status details
52 | */
53 | private String url;
54 |
55 | /**
56 | * Usually the job name in Jenkins
57 | */
58 | private String key;
59 |
60 | /**
61 | * A short name, usuallt #job-name, #build-number (will be shown as link
62 | * text in BB UI)
63 | */
64 | private String name;
65 |
66 | // Used for marshalling/unmarshalling
67 | @Restricted(DoNotUse.class)
68 | public BitbucketBuildStatus() {}
69 |
70 | public BitbucketBuildStatus(String hash, String description, String state, String url, String key, String name) {
71 | this.hash = hash;
72 | this.description = description;
73 | this.state = state;
74 | this.url = url;
75 | this.key = key;
76 | this.name = name;
77 | }
78 |
79 | public String getHash() {
80 | return hash;
81 | }
82 |
83 | public void setHash(String hash) {
84 | this.hash = hash;
85 | }
86 |
87 | public String getDescription() {
88 | return description;
89 | }
90 |
91 | public void setDescription(String description) {
92 | this.description = description;
93 | }
94 |
95 | public String getState() {
96 | return state;
97 | }
98 |
99 | public void setState(String state) {
100 | this.state = state;
101 | }
102 |
103 | public String getUrl() {
104 | return url;
105 | }
106 |
107 | public void setUrl(String url) {
108 | this.url = url;
109 | }
110 |
111 | public String getKey() {
112 | return key;
113 | }
114 |
115 | public void setKey(String key) {
116 | this.key = key;
117 | }
118 |
119 | public String getName() {
120 | return name;
121 | }
122 |
123 | public void setName(String name) {
124 | this.name = name;
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.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 com.cloudbees.jenkins.plugins.bitbucket;
25 |
26 | import java.util.LinkedList;
27 | import java.util.List;
28 |
29 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest;
30 |
31 | import edu.umd.cs.findbugs.annotations.CheckForNull;
32 | import hudson.model.Action;
33 | import jenkins.scm.api.SCMHead;
34 |
35 | /**
36 | * {@link SCMHead} extended with additional information:
37 | *
38 | * - {@link #repoOwner}: the repository owner
39 | * - {@link #repoName}: the repository name
40 | * - {@link #metadata}: metadata related to Pull Requests - null if this object is not representing a PR
41 | *
42 | * This information is required in this plugin since {@link BitbucketSCMSource} is processing pull requests
43 | * and they are managed as separate repositories in Bitbucket without any reference to them in the destination
44 | * repository.
45 | */
46 | public class SCMHeadWithOwnerAndRepo extends SCMHead {
47 |
48 | private static final long serialVersionUID = 1L;
49 |
50 | private final String repoOwner;
51 |
52 | private final String repoName;
53 |
54 | private PullRequestAction metadata = null;
55 |
56 | private static final String PR_BRANCH_PREFIX = "PR-";
57 |
58 | public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchName, BitbucketPullRequest pr) {
59 | super(branchName);
60 | this.repoOwner = repoOwner;
61 | this.repoName = repoName;
62 | if (pr != null) {
63 | this.metadata = new PullRequestAction(pr);
64 | }
65 | }
66 |
67 | public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchName) {
68 | this(repoOwner, repoName, branchName, null);
69 | }
70 |
71 | public String getRepoOwner() {
72 | return repoOwner;
73 | }
74 |
75 | public String getRepoName() {
76 | return repoName;
77 | }
78 |
79 | /**
80 | * @return the original branch name without the "PR-owner-" part.
81 | */
82 | public String getBranchName() {
83 | return super.getName();
84 | }
85 |
86 | /**
87 | * Returns the prettified branch name by adding "PR-[ID]" if the branch is coming from a PR.
88 | * Use {@link #getBranchName()} to get the real branch name.
89 | */
90 | @Override
91 | public String getName() {
92 | return metadata != null ? PR_BRANCH_PREFIX + metadata.getId() : getBranchName();
93 | }
94 |
95 | @CheckForNull
96 | public Integer getPullRequestId() {
97 | if (metadata != null) {
98 | return Integer.parseInt(metadata.getId());
99 | } else {
100 | return null;
101 | }
102 | }
103 |
104 | @Override
105 | public List extends Action> getAllActions() {
106 | List actions = new LinkedList(super.getAllActions());
107 | if (metadata != null) {
108 | actions.add(metadata);
109 | }
110 | return actions;
111 | }
112 | }
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/SCMNavigatorIntegrationTest.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 com.cloudbees.jenkins.plugins.bitbucket;
25 |
26 | import java.util.Map;
27 |
28 | import static org.junit.Assert.*;
29 |
30 | import org.junit.Rule;
31 | import org.junit.Test;
32 | import org.jvnet.hudson.test.JenkinsRule;
33 | import org.jvnet.hudson.test.TestExtension;
34 | import org.kohsuke.stapler.DataBoundConstructor;
35 |
36 | import hudson.model.ItemGroup;
37 | import jenkins.branch.MultiBranchProject;
38 | import jenkins.branch.MultiBranchProjectFactory;
39 | import jenkins.branch.MultiBranchProjectFactoryDescriptor;
40 | import jenkins.branch.OrganizationFolder;
41 | import jenkins.scm.api.SCMSource;
42 | import jenkins.scm.api.SCMSourceCriteria;
43 |
44 | public class SCMNavigatorIntegrationTest {
45 |
46 | @Rule
47 | public JenkinsRule j = new JenkinsRule();
48 |
49 | @Test
50 | public void teamDiscoveringTest() throws Exception {
51 | OrganizationFolder teamFolder = j.jenkins.createProject(OrganizationFolder.class, "test");
52 | BitbucketSCMNavigator navigator = new BitbucketSCMNavigator("myteam", null, null);
53 | navigator.setPattern("test-repos");
54 | navigator.setBitbucketConnector(SCMNavigatorTest.getConnectorMock(RepositoryType.GIT, true));
55 | teamFolder.getNavigators().add(navigator);
56 | teamFolder.scheduleBuild2(0).getFuture().get();
57 | teamFolder.getComputation().writeWholeLogTo(System.out);
58 | // One repository must be discovered
59 | assertEquals(1, teamFolder.getItems().size());
60 | MultiBranchProject, ?> project = teamFolder.getItems().iterator().next();
61 | project.scheduleBuild2(0).getFuture().get();
62 | project.getComputation().writeWholeLogTo(System.out);
63 | // Two items (1 branch matching criteria + 1 pull request)
64 | assertEquals(2, project.getItems().size());
65 | }
66 |
67 | public static class MultiBranchProjectFactoryImpl extends MultiBranchProjectFactory.BySCMSourceCriteria {
68 |
69 | @DataBoundConstructor
70 | public MultiBranchProjectFactoryImpl() {}
71 |
72 | @Override
73 | protected SCMSourceCriteria getSCMSourceCriteria(SCMSource source) {
74 | return BranchScanningIntegrationTest.MultiBranchProjectImpl.CRITERIA;
75 | }
76 |
77 | @Override
78 | protected MultiBranchProject,?> doCreateProject(ItemGroup> parent, String name, Map attributes) {
79 | return new BranchScanningIntegrationTest.MultiBranchProjectImpl(parent, name);
80 | }
81 |
82 | @TestExtension
83 | public static class DescriptorImpl extends MultiBranchProjectFactoryDescriptor {
84 |
85 | @Override
86 | public MultiBranchProjectFactory newInstance() {
87 | return new MultiBranchProjectFactoryImpl();
88 | }
89 |
90 | @Override
91 | public String getDisplayName() {
92 | return "Test multibranch factory";
93 | }
94 |
95 | }
96 |
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/WebhooksAutoregisterTest.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 com.cloudbees.jenkins.plugins.bitbucket;
25 |
26 | import java.io.File;
27 | import java.io.IOException;
28 | import java.util.logging.LogRecord;
29 | import java.util.logging.Logger;
30 | import java.util.logging.SimpleFormatter;
31 |
32 | import org.junit.Assert;
33 | import org.junit.Rule;
34 | import org.junit.Test;
35 | import org.jvnet.hudson.test.JenkinsRule;
36 | import org.xml.sax.SAXException;
37 |
38 | import com.cloudbees.jenkins.plugins.bitbucket.BranchScanningIntegrationTest.MultiBranchProjectImpl;
39 | import com.cloudbees.jenkins.plugins.bitbucket.hooks.WebhookAutoRegisterListener;
40 | import com.gargoylesoftware.htmlunit.html.HtmlForm;
41 |
42 | import hudson.util.RingBufferLogHandler;
43 | import jenkins.branch.BranchSource;
44 | import jenkins.branch.DefaultBranchPropertyStrategy;
45 | import jenkins.model.JenkinsLocationConfiguration;
46 |
47 | public class WebhooksAutoregisterTest {
48 |
49 | @Rule
50 | public JenkinsRule j = new JenkinsRule();
51 |
52 | @Test
53 | public void registerHookTest() throws Exception {
54 | RingBufferLogHandler log = createJULTestHandler();
55 |
56 | MultiBranchProjectImpl p = j.jenkins.createProject(MultiBranchProjectImpl.class, "test");
57 | BitbucketSCMSource source = BranchScanningIntegrationTest.getTestSCMSource(RepositoryType.GIT, true);
58 | source.setAutoRegisterHook(true);
59 | p.getSourcesList().add(new BranchSource(source, new DefaultBranchPropertyStrategy(null)));
60 | p.scheduleBuild2(0);
61 | waitForLogFileMessage("Can not register hook. Jenkins root URL is not valid", log);
62 |
63 | setRootUrl();
64 | p.save(); // force item listener to run onUpdated
65 |
66 | waitForLogFileMessage("Registering hook for amuniz/test-repos", log);
67 |
68 | }
69 |
70 | private void setRootUrl() throws IOException, SAXException, Exception {
71 | JenkinsLocationConfiguration.get().setUrl(j.getURL().toString().replace("localhost", "127.0.0.1"));
72 | }
73 |
74 | private void waitForLogFileMessage(String string, RingBufferLogHandler logs) throws IOException, InterruptedException {
75 | File rootDir = j.jenkins.getRootDir();
76 | synchronized (rootDir) {
77 | int limit = 0;
78 | while (limit < 5) {
79 | rootDir.wait(1000);
80 | for (LogRecord r : logs.getView()) {
81 | if (r.getMessage().contains(string)) {
82 | return;
83 | }
84 | }
85 | limit++;
86 | }
87 | }
88 | Assert.assertTrue("Expected log not found: " + string, false);
89 | }
90 |
91 | private RingBufferLogHandler createJULTestHandler() throws SecurityException, IOException {
92 | RingBufferLogHandler handler = new RingBufferLogHandler();
93 | SimpleFormatter formatter = new SimpleFormatter();
94 | handler.setFormatter(formatter);
95 | Logger logger = Logger.getLogger(WebhookAutoRegisterListener.class.getName());
96 | logger.addHandler(handler);
97 | return handler;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookProcessor.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 com.cloudbees.jenkins.plugins.bitbucket.hooks;
25 |
26 | import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
27 | import hudson.security.ACL;
28 | import jenkins.scm.api.SCMSource;
29 | import jenkins.scm.api.SCMSourceOwner;
30 | import jenkins.scm.api.SCMSourceOwners;
31 |
32 | import java.util.List;
33 | import java.util.logging.Level;
34 | import java.util.logging.Logger;
35 |
36 | /**
37 | * Abstract hook processor.
38 | *
39 | * Add new hook processors by extending this class and implement {@link #process(String, BitbucketType)}, extract owner and repository
40 | * name from the hook payload and then call {@link #scmSourceReIndex(String, String)} to launch a branch/PR reindexing
41 | * on the mathing SCMSource.
42 | *
43 | * TODO: Improvement - We could schedule a build only in the affected job instead of running a full reindex (since we
44 | * have the branch contianing the commit in the hook payload). A full reindex would be forced only when the incoming
45 | * hook is resolved to a non-existent job (new branches or new PRs).
46 | */
47 | public abstract class HookProcessor {
48 |
49 | private static final Logger LOGGER = Logger.getLogger(HookProcessor.class.getName());
50 |
51 | /**
52 | * See Event Payloads for more
53 | * information about the payload parameter format.
54 | *
55 | * @param payload the hook payload
56 | * @param instanceType the Bitbucket type that called the hook
57 | */
58 | public abstract void process(String payload, BitbucketType instanceType);
59 |
60 | /**
61 | * To be called by implementations once the owner and the repository have been extracted from the payload.
62 | *
63 | * @param owner the repository owner as configured in the SCMSource
64 | * @param repository the repository name as configured in the SCMSource
65 | */
66 | protected void scmSourceReIndex(final String owner, final String repository) {
67 | ACL.impersonate(ACL.SYSTEM, new Runnable() {
68 | @Override
69 | public void run() {
70 | boolean reindexed = false;
71 | for (SCMSourceOwner scmOwner : SCMSourceOwners.all()) {
72 | List sources = scmOwner.getSCMSources();
73 | for (SCMSource source : sources) {
74 | // Search for the correct SCM source
75 | if (source instanceof BitbucketSCMSource && ((BitbucketSCMSource) source).getRepoOwner().equalsIgnoreCase(owner)
76 | && ((BitbucketSCMSource) source).getRepository().equals(repository)) {
77 | LOGGER.log(Level.INFO, "Multibranch project found, reindexing " + scmOwner.getName());
78 | scmOwner.onSCMSourceUpdated(source);
79 | reindexed = true;
80 | }
81 | }
82 | }
83 | if (!reindexed) {
84 | LOGGER.log(Level.INFO, "No multibranch project matching for reindex on {0}/{1}", new Object[] {owner, repository});
85 | }
86 | }
87 | });
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/BitbucketSCMSourcePushHookReceiver.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 com.cloudbees.jenkins.plugins.bitbucket.hooks;
25 |
26 | import java.io.IOException;
27 | import java.util.logging.Level;
28 | import java.util.logging.Logger;
29 |
30 | import hudson.security.csrf.CrumbExclusion;
31 |
32 | import javax.servlet.FilterChain;
33 | import javax.servlet.ServletException;
34 | import javax.servlet.http.HttpServletRequest;
35 | import javax.servlet.http.HttpServletResponse;
36 |
37 | import org.apache.commons.io.IOUtils;
38 | import org.kohsuke.stapler.HttpResponse;
39 | import org.kohsuke.stapler.StaplerRequest;
40 |
41 | import hudson.Extension;
42 | import hudson.model.UnprotectedRootAction;
43 | import hudson.util.HttpResponses;
44 | import org.apache.commons.io.IOUtils;
45 | import org.kohsuke.stapler.HttpResponse;
46 | import org.kohsuke.stapler.StaplerRequest;
47 |
48 | import javax.servlet.http.HttpServletResponse;
49 | import java.io.IOException;
50 | import java.util.logging.Logger;
51 |
52 | /**
53 | * Process Bitbucket push and pull requests creations/updates hooks.
54 | */
55 | @Extension
56 | public class BitbucketSCMSourcePushHookReceiver extends CrumbExclusion implements UnprotectedRootAction {
57 |
58 | private static final Logger LOGGER = Logger.getLogger(BitbucketSCMSourcePushHookReceiver.class.getName());
59 |
60 | private static final String PATH = "bitbucket-scmsource-hook";
61 |
62 | public static final String FULL_PATH = PATH + "/notify";
63 |
64 | @Override
65 | public boolean process(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
66 | throws IOException, ServletException {
67 | String pathInfo = req.getPathInfo();
68 | if (pathInfo != null && pathInfo.startsWith("/"+FULL_PATH)) {
69 | chain.doFilter(req, resp);
70 | return true;
71 | }
72 | return false;
73 | }
74 |
75 | @Override
76 | public String getUrlName() {
77 | return PATH;
78 | }
79 |
80 | /**
81 | * Receives Bitbucket push notifications.
82 | *
83 | * @param req Stapler request. It contains the payload in the body content
84 | * and a header param "X-Event-Key" pointing to the event type.
85 | * @return the HTTP response object
86 | * @throws IOException if there is any issue reading the HTTP content paylod.
87 | */
88 | public HttpResponse doNotify(StaplerRequest req) throws IOException {
89 | String body = IOUtils.toString(req.getInputStream());
90 | String eventKey = req.getHeader("X-Event-Key");
91 | if (eventKey == null) {
92 | return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "X-Event-Key HTTP header not found");
93 | }
94 | HookEventType type = HookEventType.fromString(eventKey);
95 | if (type == null) {
96 | LOGGER.info("Received unknown Bitbucket hook: " + eventKey + ". Skipping.");
97 | return HttpResponses.error(HttpServletResponse.SC_BAD_REQUEST, "X-Event-Key HTTP header invalid: " + eventKey);
98 | }
99 |
100 | String bitbucketKey = req.getHeader("X-Bitbucket-Type");
101 | BitbucketType instanceType = null;
102 | if (bitbucketKey != null) {
103 | instanceType = BitbucketType.fromString(bitbucketKey);
104 | }
105 | if(instanceType == null){
106 | LOGGER.log(Level.FINE, "X-Bitbucket-Type header not found. Bitbucket Cloud webhook incoming.");
107 | instanceType = BitbucketType.CLOUD;
108 | }
109 |
110 | type.getProcessor().process(body, instanceType);
111 | return HttpResponses.ok();
112 | }
113 |
114 | @Override
115 | public String getIconFileName() {
116 | return null;
117 | }
118 |
119 | @Override
120 | public String getDisplayName() {
121 | return null;
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 | 4.0.0
27 |
28 |
29 | org.jenkins-ci.plugins
30 | plugin
31 | 2.5
32 |
33 |
34 | cloudbees-bitbucket-branch-source
35 | 1.9-SNAPSHOT
36 | hpi
37 |
38 | Bitbucket Branch Source Plugin
39 | https://wiki.jenkins-ci.org/display/JENKINS/Bitbucket+Branch+Source+Plugin
40 | Discover and build Bitbucket Cloud and Bitbucket Server pull requests and branches and send status notifications with the build result.
41 |
42 |
43 | MIT License
44 | http://opensource.org/licenses/MIT
45 |
46 |
47 |
48 |
49 | 1.642.3
50 |
51 |
52 |
53 | scm:git:git://github.com/jenkinsci/bitbucket-branch-source-plugin.git
54 | scm:git:git@github.com:jenkinsci/bitbucket-branch-source-plugin.git
55 | https://github.com/jenkinsci/bitbucket-branch-source-plugin
56 | HEAD
57 |
58 |
59 |
60 |
61 | org.jenkins-ci.plugins
62 | scm-api
63 | 1.2
64 |
65 |
66 | org.jenkins-ci.plugins
67 | branch-api
68 | 1.10.2
69 |
70 |
71 | org.jenkins-ci.plugins
72 | git
73 | 2.3.5
74 |
75 |
76 | org.apache.httpcomponents
77 | httpclient
78 |
79 |
80 |
81 |
82 | org.jenkins-ci.plugins
83 | mercurial
84 | 1.54
85 |
86 |
87 | org.codehaus.jackson
88 | jackson-jaxrs
89 | 1.9.13
90 |
91 |
92 | org.jenkins-ci.plugins
93 | display-url-api
94 | 0.2
95 |
96 |
97 | org.jenkins-ci.plugins.workflow
98 | workflow-multibranch
99 | 1.11
100 | test
101 |
102 |
103 | org.mockito
104 | mockito-core
105 | 1.10.19
106 | test
107 |
108 |
109 | org.hamcrest
110 | hamcrest-core
111 | 1.3
112 | test
113 |
114 |
115 |
116 |
117 |
118 | repo.jenkins-ci.org
119 | http://repo.jenkins-ci.org/public/
120 |
121 |
122 |
123 |
124 | repo.jenkins-ci.org
125 | http://repo.jenkins-ci.org/public/
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketApiConnector.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 com.cloudbees.jenkins.plugins.bitbucket;
25 |
26 | import java.util.List;
27 |
28 | import javax.annotation.CheckForNull;
29 |
30 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
31 | import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient;
32 | import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerAPIClient;
33 | import com.cloudbees.plugins.credentials.CredentialsMatcher;
34 | import com.cloudbees.plugins.credentials.CredentialsMatchers;
35 | import com.cloudbees.plugins.credentials.CredentialsProvider;
36 | import com.cloudbees.plugins.credentials.common.StandardCredentials;
37 | import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
38 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
39 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
40 | import com.cloudbees.plugins.credentials.domains.DomainRequirement;
41 | import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
42 |
43 | import hudson.Util;
44 | import hudson.security.ACL;
45 | import hudson.util.ListBoxModel;
46 | import jenkins.scm.api.SCMSourceOwner;
47 |
48 | public class BitbucketApiConnector {
49 |
50 | private String serverUrl;
51 |
52 | public BitbucketApiConnector() {
53 | }
54 |
55 | public BitbucketApiConnector(String serverUrl) {
56 | this.serverUrl = serverUrl;
57 | }
58 |
59 | public BitbucketApi create(String owner, String repository, StandardUsernamePasswordCredentials creds) {
60 | if (serverUrl == null) {
61 | return new BitbucketCloudApiClient(owner, repository, creds);
62 | } else {
63 | return new BitbucketServerAPIClient(serverUrl, owner, repository, creds, false);
64 | }
65 | }
66 |
67 | public BitbucketApi create(String owner, StandardUsernamePasswordCredentials creds) {
68 | if (serverUrl == null) {
69 | return new BitbucketCloudApiClient(owner, creds);
70 | } else {
71 | return new BitbucketServerAPIClient(serverUrl, owner, creds, false);
72 | }
73 | }
74 |
75 | @CheckForNull
76 | public T lookupCredentials(@CheckForNull SCMSourceOwner context, @CheckForNull String id, Class type) {
77 | if (Util.fixEmpty(id) == null) {
78 | return null;
79 | } else {
80 | if (id != null) {
81 | return CredentialsMatchers.firstOrNull(
82 | CredentialsProvider.lookupCredentials(type, context, ACL.SYSTEM,
83 | bitbucketDomainRequirements()),
84 | CredentialsMatchers.allOf(
85 | CredentialsMatchers.withId(id),
86 | CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(type))));
87 | }
88 | return null;
89 | }
90 | }
91 |
92 | public ListBoxModel fillCheckoutCredentials(StandardListBoxModel result, SCMSourceOwner context) {
93 | result.withMatching(bitbucketCheckoutCredentialsMatcher(), CredentialsProvider.lookupCredentials(
94 | StandardCredentials.class, context, ACL.SYSTEM, bitbucketDomainRequirements()));
95 | return result;
96 | }
97 |
98 | public ListBoxModel fillCredentials(StandardListBoxModel result, SCMSourceOwner context) {
99 | result.withMatching(bitbucketCredentialsMatcher(), CredentialsProvider.lookupCredentials(
100 | StandardUsernameCredentials.class, context, ACL.SYSTEM, bitbucketDomainRequirements()));
101 | return result;
102 | }
103 |
104 | /* package */ static CredentialsMatcher bitbucketCredentialsMatcher() {
105 | return CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(StandardUsernamePasswordCredentials.class));
106 | }
107 |
108 | /* package */ static CredentialsMatcher bitbucketCheckoutCredentialsMatcher() {
109 | return CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(StandardCredentials.class));
110 | }
111 |
112 | /* package */ List bitbucketDomainRequirements() {
113 | if (serverUrl == null) {
114 | return URIRequirementBuilder.fromUri("https://bitbucket.org").build();
115 | } else {
116 | return URIRequirementBuilder.fromUri(serverUrl).build();
117 | }
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketApi.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 com.cloudbees.jenkins.plugins.bitbucket.api;
25 |
26 | import java.util.List;
27 |
28 | import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository;
29 |
30 | import edu.umd.cs.findbugs.annotations.CheckForNull;
31 |
32 | /**
33 | * Provides access to a specific repository.
34 | * One API object needs to be created for each repository you want to work with.
35 | */
36 | public interface BitbucketApi {
37 |
38 | /**
39 | * @return the repository owner name.
40 | */
41 | String getOwner();
42 |
43 | /**
44 | * @return the repository name.
45 | */
46 | String getRepositoryName();
47 |
48 | /**
49 | * @return the list of pull requests in the repository.
50 | */
51 | List extends BitbucketPullRequest> getPullRequests();
52 |
53 | /**
54 | * @param id the pull request ID
55 | * @return the pull request or null if the PR does not exist
56 | */
57 | @CheckForNull
58 | BitbucketPullRequest getPullRequestById(Integer id);
59 |
60 | /**
61 | * @return the repository specified by {@link #getOwner()}/{@link #getRepositoryName()}
62 | * (or null if repositoryName is not set)
63 | */
64 | @CheckForNull
65 | BitbucketRepository getRepository();
66 |
67 | /**
68 | * Post a comment to a given commit hash.
69 | *
70 | * @param hash commit hash
71 | * @param comment string to post as comment
72 | */
73 | void postCommitComment(String hash, String comment);
74 |
75 | /**
76 | * Checks if the given path exists in the repository at the specified branch.
77 | *
78 | * @param branch the branch name
79 | * @param path the path to check for
80 | * @return true if the path exists
81 | */
82 | boolean checkPathExists(String branch, String path);
83 |
84 | /**
85 | * @return the list of branches in the repository.
86 | */
87 | List extends BitbucketBranch> getBranches();
88 |
89 | /**
90 | * Resolve the commit object given its hash.
91 | *
92 | * @param hash the hash to resolve
93 | * @return the commit object or null if the hash does not exist
94 | */
95 | @CheckForNull
96 | BitbucketCommit resolveCommit(String hash);
97 |
98 | /**
99 | * Resolve the head commit hash of the pull request source repository branch.
100 | *
101 | * @param pull the pull request to resolve the source hash from
102 | * @return the source head hash
103 | */
104 | String resolveSourceFullHash(BitbucketPullRequest pull);
105 |
106 | /**
107 | * Register a webhook on the repository.
108 | *
109 | * @param hook the webhook object
110 | */
111 | void registerCommitWebHook(BitbucketWebHook hook);
112 |
113 | /**
114 | * Remove the webhook (ID field required) from the repository.
115 | *
116 | * @param hook the webhook object
117 | */
118 | void removeCommitWebHook(BitbucketWebHook hook);
119 |
120 | /**
121 | * @return the list of webhooks registered in the repository.
122 | */
123 | List extends BitbucketWebHook> getWebHooks();
124 |
125 | /**
126 | * @return the team profile of the current owner, or null if {@link #getOwner()} is not a team ID.
127 | */
128 | @CheckForNull
129 | BitbucketTeam getTeam();
130 |
131 | /**
132 | * Returns the repositories where the user has the given role.
133 | *
134 | * @param role Filter repositories by the owner having this role in.
135 | * See {@link UserRoleInRepository} for more information.
136 | * Use role = null if the repoOwner is a team ID.
137 | * @return the repositories list (it can be empty)
138 | */
139 | List extends BitbucketRepository> getRepositories(UserRoleInRepository role);
140 |
141 | /**
142 | * Returns all the repositories for the current owner (even if it's a regular user or a team).
143 | *
144 | * @return all repositories for the current {@link #getOwner()}
145 | */
146 | List extends BitbucketRepository> getRepositories();
147 |
148 | /**
149 | * Set the build status for the given commit hash.
150 | *
151 | * @param status the status object to be serialized
152 | */
153 | void postBuildStatus(BitbucketBuildStatus status);
154 |
155 | /**
156 | * @return true if the repository ({@link #getOwner()}/{@link #getRepositoryName()}) is private, false otherwise
157 | * (if it's public or does not exists).
158 | */
159 | boolean isPrivate();
160 |
161 | }
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/SCMNavigatorTest.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 com.cloudbees.jenkins.plugins.bitbucket;
25 |
26 | import static org.junit.Assert.*;
27 | import static org.mockito.Matchers.any;
28 | import static org.mockito.Matchers.anyString;
29 | import static org.mockito.Mockito.mock;
30 | import static org.mockito.Mockito.when;
31 |
32 | import java.io.IOException;
33 | import java.util.ArrayList;
34 | import java.util.List;
35 |
36 | import org.junit.Test;
37 |
38 | import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient;
39 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
40 |
41 | import hudson.model.TaskListener;
42 | import jenkins.scm.api.SCMSource;
43 | import jenkins.scm.api.SCMSourceObserver;
44 | import jenkins.scm.api.SCMSourceOwner;
45 | import jenkins.scm.api.SCMSourceObserver.ProjectObserver;
46 |
47 | public class SCMNavigatorTest {
48 |
49 | @Test
50 | public void teamRepositoriesDiscovering() throws IOException, InterruptedException {
51 | BitbucketSCMNavigator navigator = new BitbucketSCMNavigator("myteam", null, null);
52 | navigator.setPattern("repo(.*)");
53 | navigator.setBitbucketConnector(getConnectorMock(RepositoryType.GIT, true));
54 | SCMSourceObserverImpl observer = new SCMSourceObserverImpl(BitbucketClientMockUtils.getTaskListenerMock());
55 | navigator.visitSources(observer);
56 |
57 | assertEquals("myteam", navigator.getRepoOwner());
58 | assertEquals("repo(.*)", navigator.getPattern());
59 |
60 | List observed = observer.getObserved();
61 | // Only 2 repositores match the pattern
62 | assertTrue("There must be 2 repositories in the team, but was " + observed.size(), observed.size() == 2);
63 | assertEquals("repo1", observed.get(0));
64 | assertEquals("repo2", observed.get(1));
65 |
66 | List observers = observer.getProjectObservers();
67 | for (ProjectObserver obs : observers) {
68 | List sources = ((SCMSourceObserverImpl.ProjectObserverImpl) obs).getSources();
69 | // It should contain only one source
70 | assertTrue("Only one source must be created per observed repository", sources.size() == 1);
71 | SCMSource scmSource = sources.get(0);
72 | assertTrue("BitbucketSCMSource instances must be added", scmSource instanceof BitbucketSCMSource);
73 | // Check correct repoOwner (team name in this case) was set
74 | assertEquals(((BitbucketSCMSource) scmSource).getRepoOwner(), "myteam");
75 | }
76 | }
77 |
78 | private class SCMSourceObserverImpl extends SCMSourceObserver {
79 |
80 | List observed = new ArrayList();
81 | List projectObservers = new ArrayList();
82 | TaskListener listener;
83 |
84 | public SCMSourceObserverImpl(TaskListener listener) {
85 | this.listener = listener;
86 | }
87 |
88 | @Override
89 | public SCMSourceOwner getContext() {
90 | return null;
91 | }
92 |
93 | @Override
94 | public TaskListener getListener() {
95 | return listener;
96 | }
97 |
98 | @Override
99 | public ProjectObserver observe(String projectName) throws IllegalArgumentException {
100 | observed.add(projectName);
101 | ProjectObserverImpl obs = new ProjectObserverImpl();
102 | projectObservers.add(obs);
103 | return obs;
104 | }
105 |
106 | @Override
107 | public void addAttribute(String key, Object value) throws IllegalArgumentException, ClassCastException {
108 | }
109 |
110 | public List getObserved() {
111 | return observed;
112 | }
113 |
114 | public List getProjectObservers() {
115 | return projectObservers;
116 | }
117 |
118 | public class ProjectObserverImpl extends ProjectObserver {
119 |
120 | private List sources = new ArrayList();
121 |
122 | @Override
123 | public void addSource(SCMSource source) {
124 | sources.add(source);
125 | }
126 |
127 | @Override
128 | public void addAttribute(String key, Object value) throws IllegalArgumentException, ClassCastException {
129 | }
130 |
131 | @Override
132 | public void complete() throws IllegalStateException, InterruptedException {
133 | }
134 |
135 | public List getSources() {
136 | return sources;
137 | }
138 | }
139 | }
140 |
141 | public static BitbucketApiConnector getConnectorMock(RepositoryType type, boolean includePullRequests) {
142 | BitbucketApiConnector mockConnector = mock(BitbucketApiConnector.class);
143 | BitbucketCloudApiClient mockedApi = BitbucketClientMockUtils.getAPIClientMock(type, includePullRequests);
144 | when(mockConnector.create(anyString(), any(StandardUsernamePasswordCredentials.class))).thenReturn(mockedApi);
145 | when(mockConnector.create(anyString(), anyString(), any(StandardUsernamePasswordCredentials.class))).thenReturn(mockedApi);
146 | return mockConnector;
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.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 com.cloudbees.jenkins.plugins.bitbucket;
25 |
26 | import static org.junit.Assert.*;
27 | import static org.mockito.Matchers.*;
28 | import static org.mockito.Mockito.*;
29 | import hudson.model.TaskListener;
30 | import hudson.plugins.git.GitSCM;
31 | import hudson.plugins.git.UserRemoteConfig;
32 | import hudson.plugins.mercurial.MercurialSCM;
33 | import hudson.scm.SCM;
34 |
35 | import java.io.IOException;
36 | import java.util.ArrayList;
37 | import java.util.List;
38 |
39 | import jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl;
40 | import jenkins.scm.api.SCMHead;
41 | import jenkins.scm.api.SCMHeadObserver;
42 | import jenkins.scm.api.SCMRevision;
43 | import jenkins.scm.api.SCMSourceCriteria;
44 | import jenkins.scm.api.SCMSourceOwner;
45 | import jenkins.scm.api.SCMSource;
46 |
47 | import org.junit.Test;
48 |
49 | import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient;
50 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
51 |
52 | import edu.umd.cs.findbugs.annotations.NonNull;
53 |
54 | public class BranchScanningTest {
55 |
56 | private static final String repoOwner = "amuniz";
57 | private static final String repoName = "test";
58 | private static final String branchName = "branch1";
59 |
60 | @Test
61 | public void uriResolverTest() {
62 | BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT);
63 | String remote = source.getRemote("amuniz", "test");
64 |
65 | // When there is no checkout credentials set, https must be resolved
66 | assertEquals("https://bitbucket.org/amuniz/test.git", remote);
67 |
68 | source = getBitbucketSCMSourceMock(RepositoryType.MERCURIAL);
69 | remote = source.getRemote("amuniz", "test");
70 |
71 | // Resolve URL for Mercurial repositories
72 | assertEquals("https://bitbucket.org/amuniz/test", remote);
73 | }
74 |
75 | @Test
76 | public void remoteConfigsTest() {
77 | BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT);
78 | List remoteConfigs = source.getGitRemoteConfigs(new SCMHeadWithOwnerAndRepo("amuniz", "test-repos", "branch1"));
79 | assertEquals(1, remoteConfigs.size());
80 | assertEquals("+refs/heads/branch1", remoteConfigs.get(0).getRefspec());
81 | }
82 |
83 | @Test
84 | public void retrieveTest() throws IOException, InterruptedException {
85 | BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT);
86 |
87 | SCMHeadWithOwnerAndRepo head = new SCMHeadWithOwnerAndRepo(repoOwner, repoName, branchName);
88 | SCMRevision rev = source.retrieve(head, BitbucketClientMockUtils.getTaskListenerMock());
89 |
90 | // Last revision on branch1 must be returned
91 | assertEquals("52fc8e220d77ec400f7fc96a91d2fd0bb1bc553a", ((SCMRevisionImpl) rev).getHash());
92 |
93 | }
94 |
95 | @Test
96 | public void scanTest() throws IOException, InterruptedException {
97 | BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT);
98 | SCMHeadObserverImpl observer = new SCMHeadObserverImpl();
99 | source.retrieve(observer, BitbucketClientMockUtils.getTaskListenerMock());
100 |
101 | // Only branch1 must be observed
102 | assertEquals(1, observer.getBranches().size());
103 | assertEquals("branch1", observer.getBranches().get(0));
104 | }
105 |
106 | @Test
107 | public void scanTestPullRequests() throws IOException, InterruptedException {
108 | BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT, true);
109 | SCMHeadObserverImpl observer = new SCMHeadObserverImpl();
110 | source.retrieve(observer, BitbucketClientMockUtils.getTaskListenerMock());
111 |
112 | // Only branch1 and my-feature-branch PR must be observed
113 | assertEquals(2, observer.getBranches().size());
114 | assertEquals("branch1", observer.getBranches().get(0));
115 | assertEquals("PR-23", observer.getBranches().get(1));
116 | }
117 |
118 | @Test
119 | public void gitSCMTest() {
120 | SCM scm = scmBuild(RepositoryType.GIT);
121 | assertTrue("SCM must be an instance of GitSCM", scm instanceof GitSCM);
122 | }
123 |
124 | @Test
125 | public void mercurialSCMTest() {
126 | SCM scm = scmBuild(RepositoryType.MERCURIAL);
127 | assertTrue("SCM must be an instance of MercurialSCM", scm instanceof MercurialSCM);
128 | }
129 |
130 | private SCM scmBuild(RepositoryType type) {
131 | BitbucketSCMSource source = getBitbucketSCMSourceMock(type);
132 | return source.build(new SCMHeadWithOwnerAndRepo("amuniz", "test-repos", "branch1"));
133 | }
134 |
135 | private BitbucketSCMSource getBitbucketSCMSourceMock(RepositoryType type, boolean includePullRequests) {
136 | BitbucketSCMSource source = new BitbucketSCMSource(null, "amuniz", "test-repos");
137 | source.setOwner(getSCMSourceOwnerMock());
138 | BitbucketApiConnector mockFactory = mock(BitbucketApiConnector.class);
139 | BitbucketCloudApiClient mockedApi = BitbucketClientMockUtils.getAPIClientMock(type, includePullRequests);
140 | when(mockFactory.create(anyString(), anyString(), any(StandardUsernamePasswordCredentials.class))).thenReturn(mockedApi);
141 | source.setBitbucketConnector(mockFactory);
142 | return source;
143 | }
144 |
145 | private BitbucketSCMSource getBitbucketSCMSourceMock(RepositoryType type) {
146 | return getBitbucketSCMSourceMock(type, false);
147 | }
148 |
149 | private SCMSourceOwner getSCMSourceOwnerMock() {
150 | SCMSourceOwner mocked = mock(SCMSourceOwner.class);
151 | when(mocked.getSCMSourceCriteria(any(SCMSource.class))).thenReturn(new SCMSourceCriteria() {
152 |
153 | @Override
154 | public boolean isHead(Probe probe, TaskListener listener) throws IOException {
155 | return probe.exists("markerfile.txt");
156 | }
157 |
158 | });
159 | return mocked;
160 | }
161 |
162 | public final class SCMHeadObserverImpl extends SCMHeadObserver {
163 |
164 | public List branches = new ArrayList();
165 |
166 | public void observe(@NonNull SCMHead head, @NonNull SCMRevision revision) {
167 | branches.add(head.getName());
168 | }
169 |
170 | public List getBranches() {
171 | return branches;
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.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 com.cloudbees.jenkins.plugins.bitbucket;
25 |
26 | import static org.mockito.Matchers.any;
27 | import static org.mockito.Mockito.mock;
28 | import static org.mockito.Mockito.when;
29 |
30 | import java.util.ArrayList;
31 | import java.util.Arrays;
32 | import java.util.Collections;
33 | import java.util.List;
34 |
35 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
36 | import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient;
37 | import com.cloudbees.jenkins.plugins.bitbucket.client.branch.BitbucketCloudBranch;
38 | import com.cloudbees.jenkins.plugins.bitbucket.client.branch.BitbucketCloudCommit;
39 | import com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest.BitbucketPullRequestValue;
40 | import com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest.BitbucketPullRequestValue.Author;
41 | import com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest.BitbucketPullRequestValueRepository;
42 | import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketCloudRepository;
43 | import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketRepositoryHook;
44 | import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketCloudTeam;
45 | import com.cloudbees.jenkins.plugins.bitbucket.hooks.BitbucketSCMSourcePushHookReceiver;
46 |
47 | import hudson.model.TaskListener;
48 | import jenkins.model.Jenkins;
49 |
50 | public class BitbucketClientMockUtils {
51 |
52 | public static BitbucketCloudApiClient getAPIClientMock(RepositoryType type, boolean includePullRequests,
53 | boolean includeWebHooks) {
54 | BitbucketCloudApiClient bitbucket = mock(BitbucketCloudApiClient.class);
55 | // mock branch list
56 | List branches = new ArrayList();
57 | branches.add(getBranch("branch1", "52fc8e220d77ec400f7fc96a91d2fd0bb1bc553a"));
58 | branches.add(getBranch("branch2", "707c59ce8292c927dddb6807fcf9c3c5e7c9b00f"));
59 | // add branches
60 | when(bitbucket.getBranches()).thenReturn(branches);
61 | if (RepositoryType.MERCURIAL == type) {
62 | withMockMercurialRepos(bitbucket);
63 | } else {
64 | withMockGitRepos(bitbucket);
65 | }
66 |
67 | if (includePullRequests) {
68 | when(bitbucket.getPullRequests()).thenReturn(Arrays.asList(getPullRequest()));
69 | when(bitbucket.checkPathExists("my-feature-branch", "markerfile.txt")).thenReturn(true);
70 | when(bitbucket.resolveSourceFullHash(any(BitbucketPullRequestValue.class)))
71 | .thenReturn("e851558f77c098d21af6bb8cc54a423f7cf12147");
72 | }
73 |
74 | // mock file exists
75 | when(bitbucket.checkPathExists("branch1", "markerfile.txt")).thenReturn(true);
76 | when(bitbucket.checkPathExists("branch2", "markerfile.txt")).thenReturn(false);
77 |
78 | // Team discovering mocks
79 | when(bitbucket.getTeam()).thenReturn(getTeam());
80 | when(bitbucket.getRepositories()).thenReturn(getRepositories());
81 |
82 | // Auto-registering hooks
83 | if (includeWebHooks) {
84 | when(bitbucket.getWebHooks()).thenReturn(Collections.EMPTY_LIST)
85 | // Second call
86 | .thenReturn(getWebHooks());
87 | }
88 | when(bitbucket.isPrivate()).thenReturn(true);
89 |
90 | return bitbucket;
91 | }
92 |
93 | public static BitbucketCloudApiClient getAPIClientMock(RepositoryType type, boolean includePullRequests) {
94 | return getAPIClientMock(type, includePullRequests, false);
95 | }
96 |
97 | private static List getWebHooks() {
98 | BitbucketRepositoryHook hook = new BitbucketRepositoryHook();
99 | hook.setUrl(Jenkins.getActiveInstance().getRootUrl() + BitbucketSCMSourcePushHookReceiver.FULL_PATH);
100 | return Arrays.asList(hook);
101 | }
102 |
103 | private static List getRepositories() {
104 | BitbucketCloudRepository r1 = new BitbucketCloudRepository();
105 | r1.setFullName("myteam/repo1");
106 | BitbucketCloudRepository r2 = new BitbucketCloudRepository();
107 | r2.setFullName("myteam/repo2");
108 | BitbucketCloudRepository r3 = new BitbucketCloudRepository();
109 | // test mock hack to avoid a lot of harness code
110 | r3.setFullName("amuniz/test-repos");
111 | return Arrays.asList(r1, r2, r3);
112 | }
113 |
114 | private static BitbucketCloudTeam getTeam() {
115 | BitbucketCloudTeam t = new BitbucketCloudTeam();
116 | t.setName("myteam");
117 | t.setDisplayName("This is my team");
118 | return t;
119 | }
120 |
121 | private static void withMockGitRepos(BitbucketApi bitbucket) {
122 | BitbucketCloudRepository repo = new BitbucketCloudRepository();
123 | repo.setScm("git");
124 | repo.setFullName("amuniz/test-repos");
125 | repo.setPrivate(true);
126 | when(bitbucket.getRepository()).thenReturn(repo);
127 | }
128 |
129 | private static void withMockMercurialRepos(BitbucketApi bitbucket) {
130 | BitbucketCloudRepository repo = new BitbucketCloudRepository();
131 | repo.setScm("hg");
132 | repo.setFullName("amuniz/test-repos");
133 | repo.setPrivate(true);
134 | when(bitbucket.getRepository()).thenReturn(repo);
135 | }
136 |
137 | private static BitbucketCloudBranch getBranch(String name, String hash) {
138 | BitbucketCloudBranch b = new BitbucketCloudBranch();
139 | b.setName(name);
140 | b.setRawNode(hash);
141 | return b;
142 | }
143 |
144 | private static BitbucketPullRequestValue getPullRequest() {
145 | BitbucketPullRequestValue pr = new BitbucketPullRequestValue();
146 | BitbucketPullRequestValueRepository source = new BitbucketPullRequestValueRepository();
147 |
148 | BitbucketCloudBranch branch = new BitbucketCloudBranch();
149 | branch.setName("my-feature-branch");
150 | source.setBranch(branch);
151 |
152 | BitbucketCloudCommit commit = new BitbucketCloudCommit();
153 | commit.setHash("e851558f77c098d21af6bb8cc54a423f7cf12147");
154 | source.setCommit(commit);
155 |
156 | BitbucketCloudRepository repository = new BitbucketCloudRepository();
157 | repository.setFullName("otheruser/test-repos");
158 | source.setRepository(repository);
159 |
160 | pr.setSource(source);
161 |
162 | pr.setId("23");
163 | pr.setAuthor(new BitbucketPullRequestValue.Author("amuniz"));
164 | pr.setLinks(new BitbucketPullRequestValue.Links("https://bitbucket.org/amuniz/test-repos/pull-requests/23"));
165 | return pr;
166 | }
167 |
168 | public static TaskListener getTaskListenerMock() {
169 | TaskListener mockTaskListener = mock(TaskListener.class);
170 | when(mockTaskListener.getLogger()).thenReturn(System.out);
171 | return mockTaskListener;
172 | }
173 |
174 | }
175 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListener.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 com.cloudbees.jenkins.plugins.bitbucket.hooks;
25 |
26 | import java.util.ArrayList;
27 | import java.util.Arrays;
28 | import java.util.List;
29 | import java.util.concurrent.ExecutorService;
30 | import java.util.concurrent.Executors;
31 | import java.util.logging.Level;
32 | import java.util.logging.Logger;
33 |
34 | import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
35 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
36 | import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook;
37 | import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketRepositoryHook;
38 |
39 | import hudson.Extension;
40 | import hudson.model.Item;
41 | import hudson.model.listeners.ItemListener;
42 | import hudson.triggers.SafeTimerTask;
43 | import hudson.util.DaemonThreadFactory;
44 | import hudson.util.NamingThreadFactory;
45 | import jenkins.model.Jenkins;
46 | import jenkins.scm.api.SCMSource;
47 | import jenkins.scm.api.SCMSourceOwner;
48 | import jenkins.scm.api.SCMSourceOwners;
49 |
50 | /**
51 | * {@link SCMSourceOwner} item listener that traverse the list of {@link SCMSource} and register
52 | * a webhook for every {@link BitbucketSCMSource} found.
53 | */
54 | @Extension
55 | public class WebhookAutoRegisterListener extends ItemListener {
56 |
57 | private static final Logger LOGGER = Logger.getLogger(WebhookAutoRegisterListener.class.getName());
58 |
59 | private static ExecutorService executorService;
60 |
61 | @Override
62 | public void onCreated(Item item) {
63 | if (!isApplicable(item)) {
64 | return;
65 | }
66 | registerHooksAsync((SCMSourceOwner) item);
67 | }
68 |
69 | @Override
70 | public void onDeleted(Item item) {
71 | if (!isApplicable(item)) {
72 | return;
73 | }
74 | removeHooksAsync((SCMSourceOwner) item);
75 | }
76 |
77 | @Override
78 | public void onUpdated(Item item) {
79 | if (!isApplicable(item)) {
80 | return;
81 | }
82 | registerHooksAsync((SCMSourceOwner) item);
83 | }
84 |
85 | private boolean isApplicable(Item item) {
86 | return item instanceof SCMSourceOwner;
87 | }
88 |
89 | private void registerHooksAsync(final SCMSourceOwner owner) {
90 | getExecutorService().submit(new SafeTimerTask() {
91 | @Override
92 | public void doRun() {
93 | registerHooks(owner);
94 | }
95 | });
96 | }
97 |
98 | private void removeHooksAsync(final SCMSourceOwner owner) {
99 | getExecutorService().submit(new SafeTimerTask() {
100 | @Override
101 | public void doRun() {
102 | removeHooks(owner);
103 | }
104 | });
105 | }
106 |
107 | // synchronized just to avoid duplicated webhooks in case SCMSourceOwner is updated repeteadly and quickly
108 | private synchronized void registerHooks(SCMSourceOwner owner) {
109 | List sources = getBitucketSCMSources(owner);
110 | for (BitbucketSCMSource source : sources) {
111 | if (source.isAutoRegisterHook()) {
112 | BitbucketApi bitbucket = source.buildBitbucketClient();
113 | List extends BitbucketWebHook> existent = bitbucket.getWebHooks();
114 | boolean exists = false;
115 | for (BitbucketWebHook hook : existent) {
116 | // Check if there is a hook pointing to us already
117 | if (hook.getUrl().equals(Jenkins.getActiveInstance().getRootUrl() + BitbucketSCMSourcePushHookReceiver.FULL_PATH)) {
118 | exists = true;
119 | break;
120 | }
121 | }
122 | if (!exists) {
123 | String rootUrl = Jenkins.getActiveInstance().getRootUrl();
124 | if (rootUrl != null && !rootUrl.startsWith("http://localhost")) {
125 | LOGGER.info(String.format("Registering hook for %s/%s", source.getRepoOwner(), source.getRepository()));
126 | bitbucket.registerCommitWebHook(getHook());
127 | } else {
128 | LOGGER.warning(String.format("Can not register hook. Jenkins root URL is not valid: %s", rootUrl));
129 | }
130 | }
131 | }
132 | }
133 | }
134 |
135 | private void removeHooks(SCMSourceOwner owner) {
136 | List sources = getBitucketSCMSources(owner);
137 | for (BitbucketSCMSource source : sources) {
138 | if (source.isAutoRegisterHook()) {
139 | BitbucketApi bitbucket = source.buildBitbucketClient();
140 | List extends BitbucketWebHook> existent = bitbucket.getWebHooks();
141 | BitbucketWebHook hook = null;
142 | for (BitbucketWebHook h : existent) {
143 | // Check if there is a hook pointing to us
144 | if (h.getUrl().equals(Jenkins.getActiveInstance().getRootUrl() + BitbucketSCMSourcePushHookReceiver.FULL_PATH)) {
145 | hook = h;
146 | break;
147 | }
148 | }
149 | if (hook != null && !isUsedSomewhereElse(owner, source.getRepoOwner(), source.getRepository())) {
150 | LOGGER.info(String.format("Removing hook for %s/%s", source.getRepoOwner(), source.getRepository()));
151 | bitbucket.removeCommitWebHook(hook);
152 | } else {
153 | LOGGER.log(Level.FINE, String.format("NOT removing hook for %s/%s because does not exists or its used in other project",
154 | source.getRepoOwner(), source.getRepository()));
155 | }
156 | }
157 | }
158 | }
159 |
160 | private boolean isUsedSomewhereElse(SCMSourceOwner owner, String repoOwner, String repoName) {
161 | Iterable all = SCMSourceOwners.all();
162 | for (SCMSourceOwner other : all) {
163 | if (owner != other) {
164 | for(SCMSource otherSource : other.getSCMSources()) {
165 | if (otherSource instanceof BitbucketSCMSource
166 | && ((BitbucketSCMSource) otherSource).getRepoOwner().equals(repoOwner)
167 | && ((BitbucketSCMSource) otherSource).getRepository().equals(repoName)) {
168 | return true;
169 | }
170 | }
171 | }
172 | }
173 | return false;
174 | }
175 |
176 | private List getBitucketSCMSources(SCMSourceOwner owner) {
177 | List sources = new ArrayList();
178 | for (SCMSource source : owner.getSCMSources()) {
179 | if (source instanceof BitbucketSCMSource) {
180 | sources.add((BitbucketSCMSource) source);
181 | }
182 | }
183 | return sources;
184 | }
185 |
186 | private BitbucketWebHook getHook() {
187 | // TODO: generalize this for BB server
188 | BitbucketRepositoryHook hooks = new BitbucketRepositoryHook();
189 | hooks.setActive(true);
190 | hooks.setDescription("Jenkins hooks");
191 | hooks.setUrl(Jenkins.getActiveInstance().getRootUrl() + BitbucketSCMSourcePushHookReceiver.FULL_PATH);
192 | hooks.setEvents(Arrays.asList(HookEventType.PUSH.getKey(),
193 | HookEventType.PULL_REQUEST_CREATED.getKey(), HookEventType.PULL_REQUEST_UPDATED.getKey()));
194 | return hooks;
195 | }
196 |
197 | /**
198 | * We need a single thread executor to run webhooks operations in background but in order.
199 | * Registrations and removals need to be done in the same order than they were called by the item listener.
200 | */
201 | private static synchronized ExecutorService getExecutorService() {
202 | if (executorService == null) {
203 | executorService = Executors.newSingleThreadExecutor(new NamingThreadFactory(new DaemonThreadFactory(), WebhookAutoRegisterListener.class.getName()));
204 | }
205 | return executorService;
206 | }
207 |
208 | }
209 |
--------------------------------------------------------------------------------